Godot Version
4.3
Question
I’m learning Godot and now I’m trying to figure out how to proper use composition with signals.
I do have an “enemy” entity with HealthComposable, HitboxComposable and so on.
I wish to notify to external scenes when that enemy dies (For handling a wave system when all entities are dead). So I emit a signal on the HealthComposable when the health reaches to zero, but how should I notify outside of the “enemy” scene?
Should I connect the parent script with the signal, and then re-emit again from the parent node? I do not know if this is a proper way or will have performance issues.
class_name HealthComposable extends Node2D
signal update_health(value: float)
signal entity_died
@export var max_health = 100
@export var min_health = 0
var health: float:
set(value):
health = value
update_health.emit(get_owner(), health)
if health <= 0:
entity_died.emit()
kill_entity()
func _ready() -> void:
health = max_health
func damage(amount: float):
health -= amount
func heal(amount: float):
health += clamp(amount, 0, max_health)
func kill_entity():
get_parent().queue_free()
HitboxComposable
class_name HitboxComposable extends Area2D
@export var health_composable: HealthComposable
signal hit
func _ready() -> void:
pass
func _on_area_entered(area: Area2D) -> void:
if area is Sword:
if health_composable:
health_composable.damage(Sword.DAMAGE)
hit.emit(Sword.DAMAGE)
What node is spawning these enemies?
You can also save the amount of enemies spawned in a static variable and whenever a enemy dies it reduces this static variable by 1
Currently I’m using a WaveManager Scene with a timer that does instantiate the enemy. I was thinking to connect to the signal from the instantiated object. But I do not have access to the signal from there.
func spawn(name: String):
var radius = 100
var direction = Vector2(randi_range(-1, 1), randi_range(-1, 1))
var new_enemy: Enemy = load(characters_path + /' + name + '.tscn').instantiate()
new_enemy.position = Game.player.global_position + direction * radius
get_tree().root.add_child(new_enemy)
you can access the enemy from the health-component (with @export for example) and call a “die()”-method and have the enemy emit a signal called “died” and then the wavespawner can connect this
I guess then there’s no way around to just pass the signal up without having to write the code on the top-level node of each entity.
Maybe I will extend from a BaseEntity class. Thanks
As i said you technically only need one signal, if i understand you correctly
Yes, I meant if I make other enemy types, I should write the die() function on each. But anyway, not a big deal. I could write the BaseEnemy and extend from it in each type of enemy.
1 Like
You could create a class that manages those signals. Make it an autoload or a singleton to access its signals from anywhere if you don’t want or can’ use static variables for this.
This way you can also connect those signals in any object.