6

I have the following Scenes: Player Enemy Attack

When an Attack collides with the Enemy, the Enemy emits a "onHit" signal.

The Player listens for that signal and bounces back.

This is all working good, but now if I duplicate the enemy so there are multiple Enemy scenes, how do I listen to the signal on all of them? Is there a way to grab all instances of a Scene and connect to all of their signals? Is there a better way of sending this message back to the Player?

I'm very new to Godot so any advice is very helpful! Thanks.

DanielRead
  • 2,288
  • 1
  • 23
  • 34

1 Answers1

17

Use a Signal Bus.

Yes, you could iterate over the nodes and find all the enemies (e.g. by comparing their script). However, it is easier if all the enemies register themselves to a list (or a group) on _ready. However, you don't need any of that.

The insight is this: An object can emit signals of other objects.

We take advantage of that by creating a Signal Bus. Which is a a common pattern in Godot. It goes as follows:

  • Create an autoload (singleton) script. Let us call it SignalBus.

  • In the script, define signals. And nothing else. *In our case, we define on_hit:

    signal on_hit
    
  • Every place that needs to emit a signal does it from the signal bus. In this case the enemies do this:

    SignalBus.emit_signal("on_hit")
    
  • And where you need to handle the signal, connect to it. For example on _ready. Something like this:

    func _ready() -> void:
        SignalBus.connect("on_hit", self, "_on_hit")
    
    func _on_hit() -> void:
        # whatever
        pass
    

This way the emitters and receivers of a signal don't need to know each other. They only need to know the Signal Bus. Which is available everywhere (by virtue of being an autoload).

Theraot
  • 31,890
  • 5
  • 57
  • 86
  • This solution worked beautifully! Thank you very much for your time explaining it! – DanielRead Aug 06 '21 at 22:34
  • Doesn't that add a layer of indirection? Suppose I need to react to a button being pressed somewhere else. Instead of attaching the handler directly to the button we now call SignalBus.emit... which will in turn call our handler. I'm a noob, just wondering if sticking to this strategy might cause performance issues down the road. – Atmaks May 21 '23 at 16:16
  • 1
    @Atmaks Correct. This adds a layer of indirection. So both parts are fully decoupled. Without it you need to reach one object from the other to be able to connect them, which be error prone and hard to maintain. Imagine, for example, every blue switch must open every blue door, and the same for other colors. As you build your scenario, you need to remember to make all the connections in the designer... You might forget, it might be hard to track when a connection is wrong. Imagine the same, but doors and switches are buried in layers of scenes, and the scenario is procedural... You get it. – Theraot May 21 '23 at 17:25
  • @Theraot Yes, I get that, but I was wandering specifically about possible performance impact. Well, I suppose one should write in C++ from the start if things like this impact performance meaningfully, right? – Atmaks May 22 '23 at 05:31
  • 3
    @Atmaks No. Optimization is not simply doing the dumb approach faster. There is value in writing GDScript first: you iterate the design faster, and you can find the performance bottlenecks by profiling. Then, if and where the performance is bad... First: find a way to accomplish the same by doing less (remove overhead, work with Servers instead of Nodes). Second: Find a way to accomplish the same by doing less often (caches, memoization, baking). Third: Find a way to accomplish the same by doing faster (port to C#, or C++, or similar). And chances are that the SignalBus is not your bottleneck. – Theraot May 22 '23 at 07:15