Why is only one of my enemies following the entire script?

Godot Version

v4.3

Question

hey, I am adding features to a survivors game i made thanks to a youtube tutorial and i added an enemy spawner. my problem is that when i spawn multiple enemies only one of them follows the entire script for some reason. they all go towards the players position but thats it. one of them always follows full script by playing attack animation and emitting the signal, the rest dont however.

i am mostly concerned to why the signal only emits once as that should solve the attack animation problem too. i connected them to my main scene perfectly fine and it works and prints the condition once the signal was connected, my only problem is that this signal is only emitted when one of the enemies die, probably the first to spawn. the print statement should be printing the updated variable every time an enemy dies heres the code.

this is in the main script:

func _ready() -> void:
	wave_apply() 
	var enemy_scene = get_node("/root/Game/demon")
	enemy_scene.connect("dead_enemy", dead_enemy_count)

func dead_enemy_count():
	print("killed")
	num_killed += 1
	print(num_killed)

this is in the enemy script:

	if health == 0:
		%ProgressBar.visible = false
		%ani_death.start() # start timer to allow death to finish
		$demon_spirte.play("die")
		dead_enemy.emit() 

result in terminal:
killed
1

why is the signal only emitted once a specific enemy dies and not for each death?
any help welcome`

Can you please give us the entire enemy script?
Enemies can’t move or attack using a script that only hides the progressbar.

1 Like

yeah sorry i should of included that here it is:

extends CharacterBody2D

signal dead_enemy

var health = 3.0
var speed = 250.0

@onready var player = get_node(“/root/Game/player”)

func _physics_process(delta: float) → void:
var direction = global_position.direction_to(player.global_position) # set position to the direction of players position
velocity = direction * speed
move_and_slide()

func take_damage():
%ProgressBar.visible = true # show health bar
health -= 1
%ProgressBar.value = health

$demon_spirte.play("hurt")
	
if health == 0:
	%ProgressBar.visible = false
	%ani_death.start() # start timer to allow death to finish
	$demon_spirte.play("die")
	dead_enemy.emit()

func _on_ani_death_timeout() → void:
queue_free()

func _on_argo_range_deal_damage() → void:
$demon_spirte.play(“attack”)

func _on_demon_spirte_animation_finished() → void:
$demon_spirte.play(“move”)

this is another node within it that i used a collision shape to register if the player was in range:

extends Area2D

signal deal_damage

func _physics_process(delta: float) → void:
var player_in_range = get_overlapping_bodies()

if player_in_range.size() > 1:
	deal_damage.emit()

the code in the main script to spawn the enemies:

func mob_spawn():
var new_mob = preload(“res://Scenes/demon.tscn”).instantiate()
%PathFollow2D.progress_ratio = randf() # set position of path to random position along path
new_mob.global_position = %PathFollow2D.global_position # set mobs spawn to this random location
add_child(new_mob) # spawn the mob

sorry if the code is messy/doesnt make sense im new to godot.

1 Like

Sorry for the late reply, but could you make sure you surrounded your reply with the ``` correctly , because for me it is currently unformatted text.

Once you do that I’ll try my best to get back to you as soon as possible!

My brain is too small to comprehend unformatted code :sob:

sorry about that i hope its formatted now. does the `` make the code look / act like it sound in the engine? also no rush replying thanks for replying in the first place.

extends CharacterBody2D

signal dead_enemy

var health = 3.0
var speed = 250.0

@onready var player = get_node(“/root/Game/player”)

func _physics_process(delta: float) → void:
var direction = global_position.direction_to(player.global_position) # set position to the direction of players position
velocity = direction * speed
move_and_slide()

func take_damage():
%ProgressBar.visible = true # show health bar
health -= 1
%ProgressBar.value = health

$demon_spirte.play("hurt")
	
if health == 0:
	%ProgressBar.visible = false
	%ani_death.start() # start timer to allow death to finish
	$demon_spirte.play("die")
	dead_enemy.emit()
func _on_ani_death_timeout() → void:
queue_free()

func _on_argo_range_deal_damage() → void:
$demon_spirte.play(“attack”)

func _on_demon_spirte_animation_finished() → void:
$demon_spirte.play(“move”)

this is another node within it that i used a collision shape to register if the player was in range:

extends Area2D

signal deal_damage

func _physics_process(delta: float) → void:
var player_in_range = get_overlapping_bodies()

if player_in_range.size() > 1:
	deal_damage.emit()

the code in the main script to spawn the enemies:

func mob_spawn():
      var new_mob = preload(“res://Scenes/demon.tscn”).instantiate()
      %PathFollow2D.progress_ratio = randf() # set position of path to random position along path
       new_mob.global_position = %PathFollow2D.global_position # set mobs spawn to this 
      random location
      add_child(new_mob) # spawn the mob
1 Like

Yes, that’s right.

Try putting three ticks on a line above and below the code so like:
tickticktick
the code
tickticktick
In this case each tick is = `
so just add three of those above and below the code, not on the same line.
It would look like

the code

Also, you can just edit one of your replies, you don’t need to reply again :slight_smile:

1 Like

This seems problematic, does you player have more than one hitbox? If not, and you are using collision masks, then you can use has_overlapping_bodies() to check if anything is overlapping the attack range.

they do, im guessing this is bad practice? they have a hitbox for collisions and one inside an Area2D which is also connected. ive attached screenshots of the collision masks and layers if that has anything to do with my issue. thanks for your reply!

the 2nd collision shape in the player node is one for the guns range that works perfectly

Nothing wrong with multiple physics bodies, and your scenes do not have multiple physics bodies; I understand the confusion with multiple collision shapes, but one is for a CharacterBody2D and the other an Area2D, which will not trigger the body_entered signal.

The thing wrong with this snippet is that it’s looking for more than one collision, not one or more. That’s where has_overlapping_bodies() will simplify the expression.

Try this instead?

if has_overlapping_bodies():
    deal_damage.emit()

Good job with the collision masks

that does make the code more simple and thanks for the help, however the signal is still only emitted once all the enemy nodes are killed. ill attach pictures since i dont think i can upload videos to the forums. i presume it has something to do with my logic on emitting the signal.

the output in the termial once all are killed:

just before i kill the final enemy theres no updates in the terminal besides current wave:

Those prints are for the dead_enemy signal right? Not damage collision related.

yeah thats right sorry if i was unclear before, the print tester that prints “i died” works everytime they die but the signal isnt emitted until they all die.

enemy script that emits the signal (signal highlighed)

extends CharacterBody2D

signal dead_enemy

var health = 3.0
var speed = 250.0
var die = false

@onready var player = get_node("/root/Game/player")

func _physics_process(delta: float) -> void:
	var direction = global_position.direction_to(player.global_position) # set position to the direction of players position
	velocity = direction * speed
	move_and_slide()
		
func take_damage():
	%ProgressBar.visible = true # show health bar
	health -= 1
	%ProgressBar.value = health
		
	$demon_spirte.play("hurt")
		
	***if health == 0:***
***		print("i died")***
***		%ProgressBar.visible = false***
***		%ani_death.start() # start timer to allow death to finish***
***		$demon_spirte.play("die")***
***		dead_enemy.emit()*** # <----- THIS IS THE SIGNAL THAT SHOULD REGSITER dead_enemy_count()

func _on_ani_death_timeout() -> void:
	queue_free()

func _on_argo_range_deal_damage() -> void:
	$demon_spirte.play("attack")

func _on_demon_spirte_animation_finished() -> void:
	$demon_spirte.play("move")

main game loop script that sould listen for the signal, signals connect on ready and the listener is at the very bottom:

extends Node2D

var wave = 0
var num_killed = 0

func _ready() -> void:
	wave_apply()
	var enemy_scene = get_node("/root/Game/demon")
	enemy_scene.connect("dead_enemy", dead_enemy_count)


func mob_spawn():
	var new_mob = preload("res://Scenes/demon.tscn").instantiate()
	%PathFollow2D.progress_ratio = randf() # set position of path to random position along path
	new_mob.global_position = %PathFollow2D.global_position # set mobs spawn to this random location
	add_child(new_mob) # spawn the mob

func wave_apply():
	%WaveNum.text = str(wave)
	match wave:
		0:
			print("wave 1")
			for i in range(3):
				mob_spawn()
			#wave += 1
			time_between_waves()
		1:
			print("wave 2")
			for i in range(8):
				mob_spawn()

func time_between_waves():
	#%WaveCooldown.start()
	pass

func _on_wave_cooldown_timeout() -> void:
	wave_apply()

func dead_enemy_count():
	print("killed")
	num_killed += 1
	print(num_killed)

Aha I see what you are looking for, thanks for highlighting that script.

So this snippet is only connecting to one node, namely the already instantiated demon in your Game scene.

You need to also connect the signal to every newly spawned mob as well.


func mob_spawn():
	var new_mob = preload("res://Scenes/demon.tscn").instantiate()
	%PathFollow2D.progress_ratio = randf()
	new_mob.global_position = %PathFollow2D.global_position

	# Connect the mob
	new_mob.dead_enemy.connect(dead_enemy_count)

	add_child(new_mob)

wow! that was a very simple fix in hindsight… i should probably flowchart important steps like these in the future. thanks a million for your time and a solution, i really appreciate it. enjoy the rest of your week :grin:

found a solution! thanks for your time and effort too :grin:

1 Like

Nice!
I’m sorry I couldn’t get back in time but at least you can format your code now! :joy:

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