Best way to create events between two nodes

:information_source: Attention Topic was automatically imported from the old Question2Answer platform.
:bust_in_silhouette: Asked By c4n4r
:warning: Old Version Published before Godot 3 was released.

Hey guys.

I’d like to know the best way to handle events between nodes.

is it possible to create a custom event, to know if a var has changed and execute something in this case?

Actually I do it in the update function, but i think it’s a bit dirty.

If you have some advices or even examples to share…

thank a lot guys!

:bust_in_silhouette: Reply From: Curly Brace

you just need to read documentation about SIGNALS (events are called so) and getters and setters.

:bust_in_silhouette: Reply From: Shavais

You know, personally I like to create my own EventManager, that is a static (global) singleton, and then use it to “listen”, “ignore” and “raise” string named events, with arbitrary (dynamically constructed) parameter objects (which can be null). It’s not appropriate for things that’re going to be happening many times per frame, but it’s good for the vast majority of purposes.

The thing about C#'s events and GDScript’s signals that falls down for me is that in order to connect up an event to a handler I have to have a reference to the object raising the event (or signalling the signal). And/or I have to declare the event delegate, et etc. I prefer to be able to simply listen for any string named event I want, anywhere in my whole code base, and raise that event anywhere else, without the listener (signaller) needing to know or care anything about the raiser (emitter), and vica versa.

I’ve done this in Python, PHP, JavaScript, C#, and now tonight I just did it again in GDScript. Here’s my EventManager script:

extends Node

var listeners = {}

func _ready():
	pass
	
# void listen(string event, funcref callback)
# adds a function reference to the list of listeners for the given named event
func listen(event, callback):
	if not listeners.has(event):
		listeners[event] = []
	listeners[event].append(callback)

# void ignore(string event, funcref callback)
# removes a function reference from the list of listeners for the given named event
func ignore(event, callback):
	if listeners.has(event):
		if listeners[event].find(callback):
			listeners[event].remove(callback)

# void raise(string event, object args)
# calls each callback in the list of callbacks in listeners for the given named event, passing args to each
func raise(event, args):
	if listeners.has(event):
		for callback in listeners[event]:
			callback.call_func(args)

I have that set up in the “autoloads” part of the project settings, so it’s globally accessible. Here is an example of listening for and raising events using this:

..
    func updateDirectionOfFacing():
    	var x = Input.get_joy_axis(0, 0)		# left joystick horizontal axis
    	var y = Input.get_joy_axis(0, 1)		# left joystick vertical axis
    	var d = getDirection(x,y)				# returns null if x and y are both 0, corrects for everything else
    	EventManager.raise("user_message", "x: %f, y: %f, d: %f" % [x, y, coalesce(d, 0.0)])
    	if (d != null):
    		dof = d
    		var rb = self.get_parent()			# this script is on the mesh, which has a rigidbody as a parent
    		rb.set_rotation_deg(Vector3(0, dof, 0))
    	pass

..

    extends Label
    
    func _ready():
    	EventManager.listen("user_message", funcref(self, "on_user_message"))
    
    func on_user_message(message):
    	self.set_text(message)
    	

oh and I forgot to mention, if I wanted to raise an event when a variable’s value changes, I might write a custom setter for that variable that would call

EventManager.raise(‘myvarname_changed’, {‘old’: oldval, ‘new’: newval})

…or something like that.

Shavais | 2017-05-21 08:44

Thank you for this very helpful code. I was looking for an event aggregator to decouple my objects and this fits perfectly.

Just one change I would suggest in the listen function (this may be due to how the forum formats our code):

func listen(event, callback):
    if not listeners.has(event):
        listeners[event] = []
    listeners[event].append(callback)

Without the unindent on the last line, only a single callback will ever be registered to a given event.

iceweasel | 2018-05-18 20:10

Yes, that is the intended (un)indentation. Good catch. I corrected the post.

Shavais | 2018-05-18 22:39

This is actually a very smart approach! Thanks for sharing the idea!

KoukouStudios | 2023-03-10 09:45