Why does an array filled in a signal receiver seem to be empty when there are multiple instances?

:information_source: Attention Topic was automatically imported from the old Question2Answer platform.
:bust_in_silhouette: Asked By plaw

I’m not sure if it’s a bug in Godot, a fault on my part, or a misunderstanding on how exactly signals work. So I decided to post my problem here.

I have the following situation:
I have an ‘enemy’ scene which consists of a parent node (type: KinematicBody) to which an Area is attached as a child. In the script of the Area I connect both body_shape_entered and body_shape_exited signals to appropriate receiver methods. whenever somethind enters or leaves the area, it’s been kept track of in an _area_cintents array. Once the damage area’s big moment has come, the AI calls a do_damage() method which inflicts damage upon all nodes within the area.

Here’s the simplified code:

var _area_contents : Array

func _ready():
    _area_contents = []
    connect("body_shape_entered", self, "_on_body_shape_entered")
    connect("body_shape_exited", self, "_on_body_shape_exited")

func _on_body_shape_entered(_body_id, _body, _body_shape, _area_shape):
    _area_contents.push_back(_body)

func _on_body_shape_exited(_body_id, _body, _body_shape, _area_shape):
    var index = _area_contents.find(_body)
    if index >= 0:
        _area_contents.remove(index)

func do_damage():
    for target in _area_contents:
        if target is Damageable:
            target.hurt()

The enemy is controlled by a small state machine which calls do_damage() from the outside if the curcumstances are right.

This works pretty well as long as there is only one instance of the ‘enemy’ scene in my level. Once I spawn multiple instances, I observed the following behaviour once I digged deeper using the debugger:

The functions _on_body_shape_entered and _on_body_shape_exited are called properly and within their scope _area_contents contains the expected nodes.

However, whendo_damage() is called on the same entity _area_contents is shown to be empty in the debugger and the function has no effect. _area_contents isn’t accessed anywhere else, only in those three functions in the Area node script.

I’m currently using Godot 3.2.2 stable.
Is this intended behaviour and I’m missing something? Or could this be a problem in Godot? Any hint on what the cause of this might be would be deeply appreciated.

Seems good to me. Are your absolutly sure that do_damage() is called on the right entity?
Have you tracked it with breakpoints?

klaas | 2020-07-17 17:14

could you be calling do_damage before _ready is called? for example in an _init or from a _ready in a parent perhaps for testing idk
you could test this by putting your _ready code in _init and see if fixed i think

rakkarage | 2020-07-17 17:45

since _init is called on construct and before the object and there children enter the tree that would make no sense.

klaas | 2020-07-17 17:53

I’ve found a workaround. Yet, I do not understand why I was observing this behaviour previously. Here’s what I changed:

The Area node, being a child of an ‘enemy’ node, has to be referenced in the ‘enemy’ node’s script, so the AI can call do_damage when the enemy attacks. I’ve applied a somewhat unnecessary pattern (a non-pattern if you will) to get the reference to the area:

export (NodePath) var DamageAreaPath
var damage_area

func initialize():
    if has_node(DamageAreaPath)
        damage_area = get_node(DamageAreaPath)

# later...

func attack():
    damage_area.do_damage()

Changing the initialize function to this, fixed the issue:

 func initialize():
    if has_node("DamageArea"):
        attack_area = $DamageArea

The project is much more complex by now and I don’t know what contributed to the problem. I tried to reproduce the wrong behaviour in a simplified test project but both of the above cases work as you would expect them to.

I would file this under human error and consider this issue resolved.
Thanks to everyone who took a look.

plaw | 2020-07-17 20:17