Signal don't call/work

Godot Version 4

gdscript

Question

Hi, I’m currently learning Godot 4 and I make a little top-down rpg. At first I started by putting all of my code in one file for both my player and my ennemy but this morning I began to create classes from which my ennemy and my player could inherit :
The structure is like this:

     Entity
        |
     IEntity
     ___|___
    |       |
 Ennemy   Player

So I just finished implementing all this stuff but it seam that all signals doesn’t work even if I connect them in my _ready()

Here’s the code for IEntity.gd :

extends Entity
class_name IEntity

#signals
signal hit(dmg: float, knockback : Vector2)


#stats
@export var health = 100
@export var damage = 10
@export var knock_back = 100

# cooldowns variables
@export_group("Cooldowns")
@export var respawn_cooldown : float = 10
@export var basic_attack_cooldown : float = 1


@export_group("Movements")
#movement-related variables
var speed = 0.0 # current_speed
@export var max_speed = 8000.0
@export var acceleration = 2000.0
@export var friction = 1500.0
@export_range(0.0, 1.0) var walk_limit := 0.75

var direction := Vector2.ZERO

#attack variables
var ennemies_in_range = {}

#state
var alive = true
var iframe = false

# respawn variables
@export_group("Respawn")
@export var start_position : Vector2

@export_group("")

#references to nodes
#cooldowns
@onready var basic_attack_cooldown_node : Timer = $BasicAttackCooldown
@onready var respawn_cooldown_node : Timer = $RespawnCooldown
@onready var animator : AnimatedSprite2D = $Animator
@onready var attackRange_node : Area2D = $AttackRange


# TODO : finish attack system, take care of multiples attacks/weapons
# TODO : adding a setting sysem file and a constructor

 # built-in methods
func _ready():
	# bind all signals
	attackRange_node.body_entered.connect(_on_attack_range_body_entered)
	attackRange_node.body_exited.connect(_on_attack_range_body_exited)
	hit.connect(take_damage)
	
	basic_attack_cooldown_node.timeout.connect(_on_basic_attack_cooldown_timeout)
	respawn_cooldown_node.timeout.connect(_on_respawn_cooldown_timeout)
	
	#set cooldowns
	basic_attack_cooldown_node.wait_time = basic_attack_cooldown
	respawn_cooldown_node.wait_time = respawn_cooldown

func _physics_process(delta):
	if health > 1:
		movement(delta)
		animation()
		move_and_slide()
	elif alive: # the entity needs to be alive in order to die
		die()


# movement-related method
func movement(delta) -> void:
	print("Create your Movement function")

# flip when going to the left
# @param left : default to true. If set to false, the sprite flip when going to the right
func enable_flip(left :bool = true) -> void:
	if velocity.x < 0:
		animator.flip_h = left
	elif velocity.x > 0:
		animator.flip_h = !left

func animation() -> void:
	enable_flip()
	if velocity.length():
		if speed > walk_limit * max_speed:
			animator.play("run")
		else :
			animator.play("walk")
	else:
		animator.play("idle")

# life-related methods
func die() -> void:
	animator.play("die")
	basic_attack_cooldown_node.stop()
	respawn_cooldown_node.start()
	alive = false

func respawn() -> void:
	alive = true
	health = 40
	position = start_position
	show()

# interactions-related methods

func attack() -> void:
	for target in ennemies_in_range.keys():
		target.emit_signal("hit", damage, dir_to(target.position) * knock_back)




#signal functions
# hitbox
func _on_attack_range_body_entered(body) -> void:
	if body.is_in_group("ennemy"):
		ennemies_in_range[body] = body.name

func _on_attack_range_body_exited(body) -> void:
	if body.is_in_group("ennemy"):
		ennemies_in_range.erase(body)

# Cooldowns
func _on_basic_attack_cooldown_timeout() -> void:
	attack()

func _on_respawn_cooldown_timeout() -> void:
	respawn()
	respawn_cooldown_node.stop()

# Animator
func _on_animator_animation_finished() -> void:
	if !alive:
		hide() # TODO: delete self (queue_free())



# own signals
func take_damage(dmg: float, knockback : Vector2) -> void:
	health -= dmg
	print(name + ".health : " + str(health))
	
	if knockback.length():
		velocity += knockback
		move_and_slide()


And here’s for Player.gd :

extends IEntity
class_name Player

var attacking = false;

# TODO : finish attack system

# player's exclusive methods
# .......

# overrided methods
#built-in methods
func _physics_process(delta):
	if health > 1:
		movement(delta)
		animation()
		move_and_slide()
		
		if Input.is_action_just_pressed("fire") && attacking:
			attack()
		
	elif alive: # play the animation once so set player_alive here
		die()


# movements-related methods
func movement(delta) -> void:
	var direction = Vector2(Input.get_axis("right", "left"), Input.get_axis("up", "down"))
	
	if direction.y:
		velocity.y = direction.y
	else:
		velocity.y = lerpf(velocity.y, 0.0, friction/max_speed) # TODO : where to add delta ?

	if direction.x:
		velocity.x = direction.x
	else:
		velocity.x = lerpf(velocity.x, 0.0, friction/max_speed) # TODO : where to add delta ?
	
	if direction.x && !direction.y:
		velocity.y = 0
	elif !direction.x && direction.y:
		velocity.x = 0
	
	# Normalize the velocity / direction
	velocity = velocity.normalized()
	
	# checking is there are inputs
	var isInputs = direction.length() > 0
	
	# Manage the speed
	# if we get Inputs, we accelerate
	if isInputs:
		speed = lerpf(speed, max_speed, acceleration/max_speed)
	else:
		speed = lerpf(speed, 0, friction/max_speed)
		if speed < 10:
			speed = 0
	
	velocity *= speed * delta

func animation() -> void:
	enable_flip()
	
	if Input.is_action_just_pressed("fire") && !attacking:
		animator.play("attack")
		attacking = true;
	
	elif velocity.length():
		if speed > walk_limit * max_speed:
			animator.play("run")
		else :
			animator.play("walk")
	
	elif !attacking:
		animator.play("idle")

# signals methods
# Animator
func _on_animator_animation_finished() -> void:
	if attacking:
		attacking = false;
	elif !alive:
		hide() # TODO: delete self (queue_free())
	print("animation finished")

And here’s the one for Ennemy.gd :

extends IEntity
class_name Ennemy

@export var test = 0

var chasing : bool = false
var target = null

# TODO : create a respawner scene
# TODO : rewrite all methods to match multiples targets

# Ennemy exclusive methods
func set_aggro(new_target) -> void:
	target = new_target


# signals methods
#built-in signals
func _on_area_2d_body_entered(body) -> void:
	if body == self:
		return
	set_aggro(get_node("../" + body.name))
	chasing = true



# overrided methods
# built-in methods
func _physics_process(delta):
	if health > 1:
		# if we have a target, make sure that he is alive
		if target:
			if !target.alive:
				chasing = false
				set_aggro(null)
			
		if chasing:
			movement(delta)
			animation()
			move_and_slide()
		else:
			animator.play("idle")
	elif alive:
		die()


# movements-related methods
func movement(delta) -> void:
	if dist_to(target.position) > 1 :
		direction = dir_to(target.position)
		velocity = direction * speed
	else:
		velocity = Vector2.ZERO
	
	velocity *= delta

func animation() -> void:
	if dist_to(target.position) > 15:
		animator.play("move")
		
		enable_flip(false)
	else:
		animator.play("attack")


# life-related methods
func die() -> void:
	animator.play("die")
	basic_attack_cooldown_node.stop()
	respawn_cooldown_node.start()
	chasing = false
	target = null
	alive = false



# signals methods
#built-in signals


# own signals
func ennemy_attack(dmg: float, knockback : Vector2) -> void:
	health -= dmg
	print(name + ".health : " + str(health))
	
	if knockback.length():
		velocity += knockback
		move_and_slide()
	
	animator.play("hurt")

It looks like your signals decided to go on vacation! :smile:

In all seriousness, the issue might be in how you’re connecting your signals in the IEntity script. Make sure those signals are hooked up to methods that exist either in the same script or one that inherits from it. If they’re not properly connected, it’s like sending out party invitations to the wrong address—nobody shows up!

Double-check your connections in the _ready() function and make sure everything’s wired up where it should be. Your signals should be back and working in no time! :rocket:

I double-check my connections and I don’t see any problems, I’ll give you the code of my IEntity without all the bodys of the method so you can just check if it work too without scrolling endlessly :
IEntity.gd

extends Entity
class_name IEntity

#signals
signal hit(dmg: float, knockback : Vector2)


# cooldowns variables
@export_group("Cooldowns")
@export var respawn_cooldown : float = 10
@export var basic_attack_cooldown : float = 1

#references to nodes
#cooldowns
@onready var basic_attack_cooldown_node : Timer = $BasicAttackCooldown
@onready var respawn_cooldown_node : Timer = $RespawnCooldown
@onready var animator : AnimatedSprite2D = $Animator
@onready var attackRange_node : Area2D = $AttackRange




func _ready():
	# bind all signals
	attackRange_node.body_entered.connect(_on_attack_range_body_entered)
	attackRange_node.body_exited.connect(_on_attack_range_body_exited)
	hit.connect(take_damage)
	
	basic_attack_cooldown_node.timeout.connect(_on_basic_attack_cooldown_timeout)
	respawn_cooldown_node.timeout.connect(_on_respawn_cooldown_timeout)
	
	#set cooldowns
	basic_attack_cooldown_node.wait_time = basic_attack_cooldown
	respawn_cooldown_node.wait_time = respawn_cooldown


#signal functions

func _on_attack_range_body_entered(body) -> void:
func _on_attack_range_body_exited(body) -> void:
func _on_basic_attack_cooldown_timeout() -> void:
func _on_respawn_cooldown_timeout() -> void:
func _on_animator_animation_finished() -> void:
func take_damage(dmg: float, knockback : Vector2) -> void:

I hope you can help me. And I also think the problem isn’t in the node references since when I launch my game, I get no erros and my player plays his animations while I don’t change the reference to my animator

1 Like

Your signal setup seems correct, but still here’s a checklist to help you troubleshoot:

Have fun Reading :muscle:

Signal Definitions: You have a hit signal in IEntity.gd and it’s connected to take_damage.

Signal Connections:

1"attackRange_node.body_entered" should be connected to “_on_attack_range_body_entered”.

2"attackRange_node.body_exited" should be connected to “_on_attack_range_body_exited”.

3hit should be connected to “take_damage”.

4"basic_attack_cooldown_node.timeout" should be connected to “_on_basic_attack_cooldown_timeout”.

5"respawn_cooldown_node.timeout" should be connected to “_on_respawn_cooldown_timeout”.

>Signal Emission: Make sure attack() is properly emitting the hit signal.
Example

func attack() -> void:
    for target in ennemies_in_range.keys():
        target.emit_signal("hit", damage, dir_to(target.position) * knock_back)

>Debugging: Add print statements in _ready() to check if connections are working:

func _ready():
    attackRange_node.body_entered.connect(_on_attack_range_body_entered)
    print("Connected body_entered signal")
    attackRange_node.body_exited.connect(_on_attack_range_body_exited)
    print("Connected body_exited signal")
    hit.connect(take_damage)
    print("Connected hit signal")
    basic_attack_cooldown_node.timeout.connect(_on_basic_attack_cooldown_timeout)
    print("Connected basic_attack_cooldown timeout signal")
    respawn_cooldown_node.timeout.connect(_on_respawn_cooldown_timeout)
    print("Connected respawn_cooldown timeout signal")

Node References: Check that attackRange_node, basic_attack_cooldown_node, and respawn_cooldown_node are correctly assigned.

> If everything looks good but you still have problems, try creating a simple test project to see if signals work there. This can help pinpoint the issue.

1 Like

it was pretty fast to check :slight_smile:
So:

  1. I indeed defined signal hit(dmg: float, knockback : Vector2)

  2. All of my connections are set properly( if there is a syntax error, gdScript tells you. Otherwise it would work but just do random func so it would be easy to debug)

2.1 : I just want to be sure but connecting a signal consist only to {node}.{signal_name}.connect({function_to_connect}) ?

  1. I can’t test rn my call to hit in attack() since it work with all other signals (some signals prepare variable to make attack() work, so therefore the hit signal)

  2. I already tested if my ready() was beeing called and indeed it was call twice, one for both my player and my ennemy.

  3. I’ll start to make this project. I send another message to let you time to think :slight_smile:

Thx for your help

I just rewrite all of my functions and calls and it work. I think I made a little mistake somewhere but I can’t tell where. thank for your help.

you tagged yourself as solution :neutral_face:

it was my first post sorry :slight_smile:
I put your message of check list as solution

1 Like

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.