I will show two approaches: one on how to create static signals and the other on arguably the "expected" approach.
Static Signals
Godot doesn't support the keywords to define a static signal. However, we know that when we load()
or preload()
some gdscript we get a container resource object of type GDScript
. A gdscript with a class_name
is symbolically bound to a GDScript
.
The gdscript parser has a sort of special treatment of these symbols. We can bypass that. Since the the symbol is actually backed by a GDScript
object we can cast it to GDScript
or an ancestor. Then we can add our static signals to the GDScript
resource that the class name is bound to.
Additionally, Godot 4 handles circular dependencies with class_name
s so this pattern actually possible.
The script with static signals:
extends Node
class_name StaticSignalsClass
static var static_signal_1: Signal = (func():
# We have to manually add a user signal.
(StaticSignalsClass as Object).add_user_signal("static_signal_1")
# Now return a reference to the signal we just defined.
return Signal(StaticSignalsClass, "static_signal_1")
).call()
# We can also define a helper static method:
static func make_signal(p_obj, p_signal_name: StringName) -> Signal:
# We use GDScript's duck typing to avoid having to cast p_obj.
p_obj.add_user_signal(p_signal_name)
return Signal(p_obj, p_signal_name)
static var static_signal_2: Signal = make_signal(StaticSignalsClass, "static_signal_2")
The client script:
static func _static_init():
StaticSignalsClass.static_signal_1.connect(func(): print("Hello, ss1."))
StaticSignalsClass.static_signal_2.connect(func(): print("Hello, ss2."))
# Somewhere else...
func emit_stuff():
StaticSignalsClass.static_signal_1.emit()
StaticSignalsClass.static_signal_2.emit()
"Expected" Signal Center
We define an autoload that is the center for our signals. You can create namespaces by
- Adding a property that holds a script with more signals.
- Follow the
RenderingServer
pattern with the namespace in the name.
The caveat is to be mindful of the order autoloads are loaded. If an earlier initialized autoload accesses SignalCenter
it will fail. (However, on _ready
will work.)
extends Node
# autoload name: SignalCenter
signal chatlog_new_message(msg: String)
signal chatlog_user_joined(guest_id: int)
signal chatlog_user_left(guest_id: int)
# Or
var chatlog := ChatLogSignalCenter.new()
### ChatLogSignalCenter script
extends Object
signal new_message(msg: String)
signal user_joined(guest_id: int)
signal user_left(guest_id: int)
The client script:
extends Node
class_name StaticClient
static func _static_init():
SignalCenter.chatlog_new_message.connect(func(): print(msg))
# or
SignalCenter.chatlog.new_message.connect(func(): print(msg))