I’m currently going through a tutorial on Godot. As I’m working through signals, I’m not exactly happy with how signals are set up. Namely, I’m not happy with the multiple methods of setup and the way it’s set up in code using strings.
I wanted to get a sense of how others used signals.
I’m never a fan of strings. It’s easy for typos, makes refactoring more difficult, and more. Does anyone do anything to aid with this, something like:
# constants.gd
var SIGNAL_NAME = "signal_name"
signal signal_name
# object.gd
emit_signal(SIGNAL_NAME)
Even this feels weird, but it keeps the refactoring/possible mistake spot close together. Or is this a YAGNI thing and folks typically stick with passing a string name around?
Do you prefer to keep your signal setup in code or use the interface (storing in the scene file)? I’m leaning towards instantiate all in code so I know where to find them. However, maybe folks find it easy to just find across both the scene and code files.
#Constants:
signal my_signal
signal my_signal_with_params(a: int, b: int)
#Object:
Constants.my_signal.emit()
Constants.my_signal_with_params.emit(1,5)
Edit: Works only if Constants is an autoload or are reference to an instance. Not sure if you wanted to emit signal from something that is not in the scene tree.
Thank you, I kind of like that solution. I do see what you mean, and do see some problems with a constants file approach. It’d mean all the contents would get loaded per script, possibly solvable by breaking apart into object1_constants, object2_constants, etc. Which then feels like it could go into YAGNI territory…
I think I’ll play with doing it the way you’re suggesting and see how it feels.
I would say use the my_signal.emit(...) version and whenever possible declare the signal in the class that is emitting it. No need for a separate file. Though, I think it’s reasonable to have a signal bus, too, which consolidates “homeless” signals.
you don’t use strings with signals, not in godot 4.
As seen above, the recommended method to connect signals is not connect. The code block below shows the four options for connecting signals, using either this legacy method or the recommended Signal.connect, and using either an implicit Callable or a manually defined one.
func _ready():
var button = Button.new()
# Option 1: Object.connect() with an implicit Callable for the defined function.
button.connect("button_down", _on_button_down)
# Option 2: Object.connect() with a constructed Callable using a target object and method name.
button.connect("button_down", Callable(self, "_on_button_down"))
# Option 3: Signal.connect() with an implicit Callable for the defined function.
button.button_down.connect(_on_button_down)
# Option 4: Signal.connect() with a constructed Callable using a target object and method name.
button.button_down.connect(Callable(self, "_on_button_down"))
func _on_button_down():
print("Button down!")
While all options have the same outcome (button’s BaseButton.button_down signal will be connected to _on_button_down), option 3 offers the best validation: it will print a compile-time error if either the button_downSignal or the _on_button_downCallable are not defined. On the other hand, option 2 only relies on string names and will only be able to validate either names at runtime: it will print a runtime error if “button_down” doesn’t correspond to a signal, or if “_on_button_down” is not a registered method in the object self. The main reason for using options 1, 2, or 4 would be if you actually need to use strings (e.g. to connect signals programmatically based on strings read from a configuration file). Otherwise, option 3 is the recommended (and fastest) method.