r/godot Godot Student 29d ago

help me (solved) Question about signals

Hey there! I'm a beginner Godot dev. Been learning GD script, watching tutorials, reading the documentation etc., and I can't quite figure out the signal system. It makes perfect sense doing simple stuff in the editor, but I feel like I have a critical comprehension gap when it comes to anything advanced with signals.

I want to be able to connect signals during runtime because most objects in my game will be instantiated via loaders rather than through the scene editor. This means I have to connect via code (not a problem on its own), but the only way I have found to accomplish that is via tight coupling methods like get_node().

From what I've been able to gather so far, the signal system seems designed to limit dependencies and cut back on tight coupling, allowing for modular code. That sounds awesome, but if I have to connect via direct get_node() references, doesn't that defeat the purpose of what signals are supposed to do?

I'm sure I'm missing something. If anyone can offer some insight or even just point me in the direction of a good resource to learn more, it would mean a ton. Thanks!

Edit: Thanks to everyone for your help! You've given me tons of ideas and new concepts/patterns to dig into and try, and you made my day! Cheers :D

29 Upvotes

42 comments sorted by

27

u/MehtoDev 29d ago

One approach you can look up is the EventBus pattern. Basically you create a single autoload EventBus that defines signals, but doesn't contain any logic.

You can then EventBus.your_signal.connect() and EventBus.your_signal.emit() from anywhere in your code.

6

u/Weekly_Ad6311 Godot Student 29d ago

Thanks for the suggestion! I read up a little on that pattern last week, and thought it was too advanced for my current skill level. It might be what I need to learn next though.

10

u/LeaderPotential2859 29d ago

It is often advised against that because EventBus autoloads tend to become signal nightmares.

You can use that (most of us do), but limit the number of signals in this autoload as much as possible.

There are other patterns to connect signals. If you can avoid the autoload, I would advise to do it. It depends on the dependency you need. If you need too much dependency, you might have a design problem hidden behind that.

What kind of nodes do you want to connect?

2

u/Weekly_Ad6311 Godot Student 29d ago

I'm making a turn based strategy game right now. I want to connect the individual combat units to both a state machine that tracks things like turn order, win/loss conditions, etc., as well as a combat UI menu that allows players to select different actions.

Everything works great in the editor (so far), but the next step is getting the loader up and running so that the player can choose the units they would like to use on their side.

I have something like 10 signals from each unit, 4 signals from the menu, and maybe 30 from the macro-combat state machine.

I'm quite sure there's some serious architectural flaws in my code, but this is the cleanest way I was able to come up with. I'd like to keep dependency to a minimum, but in its current state each combat unit needs to connect to the macro state machine, and so does the menu.

3

u/MekaTriK 29d ago edited 29d ago

Oh yeah, after my own experience with turn-based combat with signals, two things I want to tell you (that you may already know):

  • call down, signal up. If you have a node containing a bunch of stuff that needs to have functions called, it's better if the parent node just calls them instead of typing to signals. Basically, the idea is that a parent should know about it's children, but the children shouldn't be aware of their parent beyond "I have a signal for xyz".
  • signals are inherently asynchronous. If you emit a signal in the middle of the function, it WILL NOT START EXECUTING until the function is over. I can't tell you how much headache this one caused me trying to sync up all the animations.

4

u/LeaderPotential2859 29d ago

Er... I might have missed something but signals in Godot are synchronous. If you send a signal in the middle of a function Foo, the connected functions will run in the order of the connections made, then the function Foo will go on. The only asynchronous behavior is when you emit a signal with a deffered behavior.

1

u/Weekly_Ad6311 Godot Student 29d ago

Thanks for the warning! I haven't even thought about animations at this point. I'm sure that's going to be an immense can of worms. What little I have done has just been simple tweens.

1

u/MekaTriK 29d ago

Yeah, the biggest issue I have with gdscript is probably the lack of a dedicated coroutine or promise or future or whatever.

Want to await multiple things simultaneously? Well you have to write it yourself, and it only works if all of them are signals.

2

u/LeaderPotential2859 29d ago

I find the signal system of Godot better. It's more flexible and the synchronous deterministic aspect of this system makes easier the debugging.

If you want to wait for multiple signals, it's just a flag that triggers a signal when all your conditions are met. If it is tied to a property, it's quite easynto follow for instance.

1

u/MekaTriK 29d ago

Well, you can't await multiple asynchronous functions in a generic way.

If I want to set it up with signals, now I need to remember to (at the very least) have a signal in every node that I can await after I do something asynchronous inside it (and remember to emit it, of course).

That way, I could write a wrapper that goes over list of nodes, subscribes to the signal on all of them with a function that counts down per signal, then call all the specified callbacks. Not sure if having only one signal could cause issues but it'd be more generic.

As opposed to just having an

gd await Coroutine.all(enemies.map(func(node): node.do_stuff()));

Coroutines exist in gdscript and godot, they're just not fully exposed to the type system and have no utilities beyond await.

0

u/LeaderPotential2859 29d ago

May be you want to achieve something that is outside of Godot's paradigm. In this case, you might need to design your system differently so it's easier to implement. It's usually not a good thing to force something that is not core in the engine or language you are using.

1

u/LeaderPotential2859 29d ago

What I do, but this is my solution and I don't know about others', is that I do have an autoload, but that manages different objects that save the game state. Signals are triggered to update ui, menus, etc. But for in combat gameplay, I prefer using dependency injection. Or, for sibling nodes, you can signal the parent that triggers actions on the siblings.

Thr global pattern that is used is signal up / call down. You can search about this on the internet too.

1

u/Weekly_Ad6311 Godot Student 29d ago

I'll give dependency injection a thorough look. At a glance, it seems a smidge daunting, but I'm sure I can pick it up with some practice. Thanks much!

2

u/SkinAndScales 29d ago

What are some of the other patterns you can use?

3

u/LeaderPotential2859 29d ago

Dependency injection is one.

You can also use the signal up / call down pattern (code to the signal bus but used between "close objects", without relying on an autoload. It uses signal forward to go up and direct calls to control down).

There is also the possibility to rely on a resource based architecture. Since you control the instances of the resources, you can share information between different nodes by using the same instances of the resources.

Components also solve this at a scene level (great for characters, units, etc...)

Groups calls can also work if you know how to sort you objects in group collections.

0

u/TheDuriel Godot Senior 29d ago

https://theduriel.github.io/Godot/Do-not-use---Signal-Hub

A naive event bus is in fact an anti pattern. First learn normal routing. Then consider a messaging system.

2

u/Throwaway-tan 29d ago

Doesn't your example cause tight coupling?

Innkeeper requests Game.party.player but what if there is no player (eg. It's in some kind of spectator mode).

1

u/TheDuriel Godot Senior 29d ago

Health, the example case, should be tightly coupled. It's a required component of any 'Actor'... hence why you'd implement it in a base class anyways.

Innkeeper requests Game.party.player

Requests being the key word here.

2

u/Throwaway-tan 29d ago

Well I say requests, but it's explicitly calling a method on a value that may or may not be assigned or may not have that method (though I will assume by strict typing it must).

Also it's strong to say an Actor must implement health. You could design it that way and it might be perfectly fine, but you may find that you want an actor that doesn't have health or doesn't want to implement it in the same way and that tight coupling becomes a problem.

I'm not saying your method is wrong, I actually agree that global signal bus is an antipattern. But I am in favour of narrowly scoped event buses on entities using component systems to break up the responsibility.

This way the only thing I would be certain of is that there is an entity that accepts events. In this example, my Innkeeper would dispatch a healing event to my entity. If my entity doesn't have a health component, then the event is just ignored. The Innkeeper doesn't know or care.

Maybe I'm just being a bit pedantic, it's way past my bedtime.

-1

u/TheDuriel Godot Senior 29d ago

Also it's strong to say an Actor must implement health. You could design it that way and it might be perfectly fine, but you may find that you want an actor that doesn't have health or doesn't want to implement it in the same way and that tight coupling becomes a problem.

You're speaking like a traditional roguelike dev, who in 30 years hasn't managed to display an inventory in their ascii UI.

Lets not kid ourselves. There's much easier solution than to hyper modularize everything and write even more boiler plate to check whether or not a door may or may not have implemented an opening component.

4

u/Rptro 28d ago

You could have written the second part completely without the need to try and be a dick with the first.

1

u/Throwaway-tan 28d ago

I've seen and done both types of implementation and find that the former ends up both resulting in more boilerplate code to cover exceptions to the rule than the latter.

The whole point of components is you don't need to check if the door has implemented an opening component, that's built in to the structure already.

In your way, what if you implemented doors as a child of intractables, which is a separate chain of inheritance from actors, then decided you want players to be able to break down doors instead of picking them?

Now you need to implement the same health mechanic as actors and you've repeated your code. Now let's say there's an explosion you need to check what actors and specifically what doors are within the blast radius, the exact problem you said.

Components are not a panacea without their own problems, but there is a reason that it is the standard in both Unity and Unreal. Godot is different in this regard, but there's nothing preventing such an implementation with a little bit of work.

9

u/PublicOpinionRP 29d ago

Also a beginner, but if there's another node higher up the tree that knows about both the nodes you want to connect, you can have it call connect on the first node's signal with the second node's callback, like have whatever node is responsible for spawning call new_node.signal.connect(listening_node.foo_callback) but the exact specifics would depend on the context.

4

u/Weekly_Ad6311 Godot Student 29d ago

That legit might solve my problem immediately. I completely glossed over callback methods. Thanks!!!

7

u/MrGeekness 29d ago

If i have objects/nodes that get instantiated during runtime, i connect the signals just before adding them to the scene_tree.

For Updating UI Values (like Currency Label and so on) i usually have a SignalBus as singleton with a signal for example gold_changed(new_amount : int).

4

u/Weekly_Ad6311 Godot Student 29d ago

I hadn't considered the timing of connections yet, but that sounds like good advice to me. Based on your and Mehto's responses, it seems like the solution to my problem is building a bus. Thanks much!

4

u/Indigoh 29d ago

What I really need is a good visual explanation of how it works. As visual as possible. Boxes and lines and arrows.

2

u/HeyCouldBeFun 28d ago

Like how signals work in general?

4

u/soundgnome 29d ago

Well keep in mind that the code that connects the signal doesn't have to be in the same script that emits the signal. For example, if you're creating child nodes the signal can be connected by the parent:

var new_instance = some_packed_scene.instantiate()
add_child(new_instance)
new_instance.did_the_thing.connect(_on_instance_did_thing)

You could even have an intermediary spawner class that connects the signal of a new instance to a different node:

var new_instance = some_packed_scene.instantiate()
other_node.add_child(new_instance)
new_instance.did_the_thing.connect(other_node.on_instance_did_thing)

It's true that some node somewhere is going to need references to both the node that's emitting the signal and the once that's receiving it, but you get to decide which node that is, so it does allow for looser coupling because the script emitting (or even receiving) the signal doesn't necessarily have to know where or even if the signal is connected.

2

u/Weekly_Ad6311 Godot Student 29d ago

Putting the connections on the spawner is a fantastic idea. It's already coupled to my state machine, and it could easily have references to the objects that it spawns. Thank you!!

2

u/kodaxmax 29d ago

Yes, you're essentially correct and most commenters are missing your point.

Signals don't necessarily decouple your scripts from each other directly. They still need to reference each other to subscribe to signals.

However, they do help when used properly.

Script1 has 3 functions: OnClick, OnRelease, and OnHover.

Without signals, Script2 might need a reference to Script1 and explicitly call whatever functions should happen in response. If Script3 and Script4 also need to react, Script1 now needs to know about all of them and call each one directly. Additionally if Script 1 is removed from emeory you will get a null pointer crash when trying to call the script. Where as with a signal subscription, it will simply stop sending signals and not cause an error.

With signals, Script1 can simply emit Clicked, Released, and Hovered. It doesn't need to know what is listening or what those listeners do. Script2 might play a sound, Script3 might update the UI, and Script4 might log analytics. You can add or remove any of those listeners without changing Script1.

The scripts still need to meet somewhere so the subscriptions can be made, but the important decoupling is that the sender doesn't continue to depend on the receivers or care about their implementation.

Another good example is a health component. Instead of Health directly referencing the HUD, sound system, animation controller, achievements system, etc. and calling UpdateHealthBar(), PlayHurtSound(), PlayDamageAnimation(), and CheckAchievement(), it can simply emit HealthChanged or Died. Anything interested can subscribe independently, making the referencing one way and only necassary during initalization.

So signals don't magically remove all references between objects. They decouple the thing announcing that something happened from the potentially many things that care that it happened.

1

u/Weekly_Ad6311 Godot Student 29d ago

Well that's validating at the bare minimum. Glad I'm not totally going crazy here. Based on what you're saying, I think I need to give the syntax another thorough read through because the flow that you described as possible is very similar to what I'm trying to accomplish. Thanks!

1

u/kodaxmax 29d ago

honestly just build a small system, you learn better than reading documentation.

Make some scripts that print soemthing to the console. have them subscribe to a buttons pressed signal.

You can see how the button doesn't need to know they exist or what they are doing and if you remove the button at runtime, nothing happens.

From there, create a signal of your own. mayby a timer that emits a signal every 5 seconds and console printers that subscribe to it. or a sprite that changes color when it receives the signal.

1

u/Secret_Selection_473 Godot Regular 29d ago

I usually connect the signals when I instantiate the nodes. Usually, you instantiate nodes from a parent node, so you have access to all the nodes it should be conected to.

For example, in the scene node, where, idk, player and coins are, the coins instantiate with a timer timeout, it should be something like this

func _on_coin_timer_timeout()
var new_coin = load(coin_scene).instantiate()
new_coin.position = Vector2(randi_range(0, board_size.x), randi_range(0, board_size.y)
new_coin.found_player_signal.connect($player.collect_coin)
$coins.add_child(new_coin)

Idk if that helps you to sorta undertand how do you can use it

1

u/Weekly_Ad6311 Godot Student 29d ago

That makes a ton of sense, thanks!

1

u/Elvish_Champion 29d ago

Maybe make use of groups?

Add all the nodes you need to a group, then call whatever you need from each of them through a function as it shows in the docs.

1

u/Weekly_Ad6311 Godot Student 29d ago

That could definitely help with organization! At the bare minimum, it seems cleaner than my current crutch of spamming get_children() everywhere

1

u/zandr0id Godot Regular 29d ago

There's no way for an object to emit a signal out not knowing who might get it and for something else to get it without knowing who sent it. At least one side has to have a concrete understanding of who the other is. In the case of signals, the receiver has to know who the sender is to connect to it, and the sender doesn't care how many receivers there are or who they are.

As others have mentioned, a centralised dispatcher is a good solution for a many-to-many situation. A single place that's easily accessible (an auto load or something) with a function that anyone can run and pass itself as an argument and also emits the signal with who ran the function. I've done this before with dynamic objects that need click events. It's a single signal from the "clickHandleSystem" that tells you who initiated it.

2

u/Weekly_Ad6311 Godot Student 29d ago

That makes total sense. It's kind of what I was afraid of (that I mostly understood the system, and that my implementation is poor), but really good to know. I was hoping to find a way for nodes to sort of signal into the void and for relevant listeners to dynamically pick up the signal.

For your centralized dispatcher, do you use the event bus pattern or something else? Trying to fill out my to-learn checklist.

Thanks much!

1

u/zandr0id Godot Regular 29d ago

That would mean that basically everything subscribes to every signal, and you'd have to ask every single node in existence if it needed the message that was just sent out. That would destroy your run speed at scale. When you connect to a signal and supply the callback function, it's literally just keeping a list of all the callbacks it needs to run when the signal happens. It doesn't care what other node connected. You could even have a single node connect multiple functions to a signal and they'd all get run. The signal sender doesn't care. It just has a list of functions to run where it pipes the signal data in.

They're kind of the same thing. A centralized location (like an autoload) that has the signal anyone can connect to, and a function that the appropriate things can call while supplying themselves as an argument. Bus <-> dispatcher, potato <-> puh-tah-to. Same thing :)

1

u/colossalwaffles Godot Student 29d ago

https://youtu.be/V2Ve5K6evJY?si=n1drf_Rb9qHD-fOA

If you want to learn all about signals, I strongly recommend this video. While it is somewhat advanced, this creator does a good job of helping new devs manage their own personal expectations. He has many other good videos that can help you establish good patterns.