Decoupled (pub-sub) messaging system in Godot

posted in jhocking
Published November 24, 2023 Imported
Advertisement

This is an explanation for how to make a decoupled messaging system using the built-in “signals” mechanism. I just started learning GDScript to do programming in Godot, and it isn’t taking me long at all. I quickly realized “signals” are a nice way of handling events, but have the downside that the object receiving events needs to know about the object that sends events. This level of coupling is fine in many situations, but a truly decoupled system (where neither the sender nor receiver know about each other) would often be even better, so I devised this simple approach to doing that.

At a high-level, you make a script with a list of your signals in it, and then set that script to Autoload (making sure Global Variable is enabled, to get the convenient variable name). In other words, something like this in the Project Settings:

I named my script PubSub in reference to the Publish-Subscribe messaging pattern. Obviously name it whatever is most meaningful to you (eg. I’ve seen this called EventBus or even just Messenger), but I generally refer to these as “pub-sub systems” so there’s the name for me.

This script needs to extend Node in order to Autoload, but otherwise doesn’t need any functions, just a list of signals. That’s because this script isn’t going to do anything on it’s own; rather, it just acts as an intermediary between senders and receivers. For example, here is PubSub.gd in my test:

extends Node

signal level_completed
signal player_harmed(amount:int)

The actual list of signals will depend on your game, and can be expanded at will as you develop the game. Whenever you want a new event, just add a new signal to the list!

Alright so that’s how you create the pub-sub object. Now actually using it is very simple: any other classes can call connect() to subscribe (or listen, or receive or whatever you wanna call it) to that event, and anyone can call emit() on the signal to publish (or broadcast etc.) an event. Here’s an example for the script that subscribes:

extends Node3D
 
func _ready():
	PubSub.level_completed.connect(on_complete)
	PubSub.player_harmed.connect(on_harm)
 
func on_complete():
	print("Congratulations!")
 
func on_harm(amount:int):
	print("oof %d" % amount)

And here’s an example for the script that publishes:

extends Node3D

func _process(delta):
	if Input.is_action_just_pressed("ui_accept"):
		PubSub.level_completed.emit()
	if Input.is_action_just_pressed("ui_left"):
		PubSub.player_harmed.emit(2)

Nice easy syntax! And note that I did two sample signals in my little test, one with parameters and one without. Just hit the spacebar or left arrow to see the console output.

Read more

0 likes 0 comments

Comments

Nobody has left a comment. You can be the first!
You must log in to join the conversation.
Don't have a GameDev.net account? Sign up!
Profile
Author
Advertisement
Advertisement