Remember enemy deaths between rooms

Godot Version

v4.2.1.stable.official [b09f793f5]

Question

In my game, the player can explore and transition between different rooms (scenes) and each room has its own set of enemies inside.
I want to create a simple system where if an enemy is killed in one room, then the player leaves and later returns to the room, the game will remember that this enemy has been killed and will not spawn it.

My current implementation of this was to create an autoload that will store the name of the room and an ID of the killed enemy in a dictionary, and once the player returns to that room, the enemy will check if its ID is registered in that dictionary like so:

Audoload (GlobalSave):

var defeated_enemies := Dictionary()

func insert_defeated_enemy(scene_name: String, id: int) -> void:
	if defeated_enemies.has(scene_name):
		defeated_enemies[scene_name].append(id)
	else:
		defeated_enemies[scene_name] = [id]

func check_defeated_enemies(scene_name: String, id: int) -> bool:
	if defeated_enemies.has(scene_name):
		return defeated_enemies[scene_name].has(id)
	return false

Enemy class:

class_name Enemy

@export var id: int

func _ready() -> void:
	if GlobalSave.check_defeated_enemies(get_node(GlobalPaths.ROOM).scene_file_path, id):
		queue_free()

func _on_enemy_death() -> void:
	GlobalSave.insert_defeated_enemy(get_node(GlobalPaths.ROOM).scene_file_path, id)
	queue_free()

This works well for the most part, however you may notice that the id variable is an export, meaning that when I create my rooms, I have to manually assign IDs to every enemy I add and make sure no two enemies in the same room share the same ID.

I had another idea which was to add a script to my room scenes and have it assign IDs to all of the enemies inside itself:

func _ready() -> void:
	var set_id := 0
	for enemy: Enemy in $Enemies.get_children(): #$Enemies is just a holder node which contains all enemies in the room
		enemy.id = set_id
		set_id += 1

However this did not work, because for some reason, the _ready function of the enemies runs before the _ready function of the room (even though the enemies are grandchildren of the room scene?)

So that’s why I came here. Could anyone help me figure out how to automatically assign IDs to enemies and store them?

how the enemy is being spawn?

the “enemy spawner” or “enemy controller” instead should tell if in this scene/room the enemy should spawn or not by checking the global autoload.

so, it’s not when the enemy is spawned, you kill it right away by queue free

That’s how it works, _ready() is called on children first, on the parent last.

What happens in the children _ready() function that makes your code “not work”? From the look of it the room should correctly distribute ids to the children once they are ready.

I’m just placing my enemies in the room scenes, there’s no spawner

Because when the player reenters the room after killing some enemies, the enemy will check if its ID is in the autoload before the room could have a chance to distribute IDs

I see. You can provide a room function to get an id like this:

# room.gd

var last_id := 0

func get_available_id() -> int:
    var id := last_id
    last_id += 1
    return id

And call it from enemies when they want to get a unique Id for themselves.

You can do this idea, but please move this code into a singleton, call it anything, EnemyManager or EnemyIDDistributor, doesn’t matter. Autoload as a global from project setttings.

But never call a room method from an enemy because it creates a circular reference in your code. Room → enemy, enemy → room. This is how memory leaks are created. I did read that Godot should catch this and push an error to debugger, but I haven’t tested it. Either way - a major no-no, one of the few code practices that you can consider law.

With love,

1 Like

If this is the only thing that a room does (and judging from OP description it is), then there is no reference from a room to enemies, only from enemies to the room.

He’s adding Enemy objects to the room in the scene tree, therefore there are references from Room to many Enemy scenes.

If I do put this code in an autoload, how can I make sure that enemies get the same ID every time? Because the way I see it it would just assign IDs like this:

Player enters room 1:
EnemyOne: ID 0
EnemyTwo: ID 1

Player enters room 2:
EnemyOne: ID 2
EnemyTwo: ID 3

Player reenters room 1:
EnemyOne: ID 4
EnemyTwo: ID 5

EDIT: I could just reset last_id back to 0 when I transition rooms I suppose

1 Like

Circular dependencies were solved in Godot 4.0 alpha, you shouldn’t worry too much about it anymore. Fix issues when they appear, not when you think then appear.