+2 votes

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!

asked Dec 12, 2016 in Engine by c4n4r (48 points)

2 Answers

+1 vote

you just need to read documentation about SIGNALS (events are called so) and getters and setters.
http://docs.godotengine.org/en/latest/reference/gdscript.html#signals
http://docs.godotengine.org/en/latest/reference/gdscript.html#setters-getters

answered Dec 12, 2016 by Curly Brace (43 points)
+2 votes

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)
answered May 21, 2017 by Shavais (40 points)
edited May 18, 2018 by Shavais

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.

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.

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

Welcome to Godot Engine Q&A, where you can ask questions and receive answers from other members of the community.

Please make sure to read How to use this Q&A? before posting your first questions.