Events Messaging System question

Hi there everyone,

Having a new project in Unity after a few years and there’s changes/enhancements to catch up to:

Am I right to assume that this method

is (now) preferable to this method

?

I used the latter in the last project but just looking at the code the former looks much “cleaner” to me?

Thanks in advance /Carsten

Ah yes, some more research reveals the rabbithole of pros and cons.
For future people stumbling upon this thread with similar questions:
https://www.reddit.com/r/Unity3D/comments/35sa5h/unityevent_vs_delegate_event_benchmark_for_those/

https://stackoverflow.com/questions/42034245/unity-eventmanager-with-delegate-instead-of-unityevent

That live training, your second link, was not good. Aside from the presentation issues, the instructor teaches a mediator pattern using a Monobehaviour class (without any apparent reason for using Monobehaviour) that uses C# constructors for a singleton (not best practice in Unity) for the event manager, using UnityEvents (which are only used over C# events/actions because they’re visible in the editor) in a dictionary (which is not visible in the editor) with strings (which isn’t best practice for anything) and a system for creating new string-based dictionary entries on the fly (which is asking to create errors and lose track of things).

I looked at a looooot of posts and tutorials for setting up a messaging system in Unity. That live training was the worst approach I found. It’s both overengineered and brittle. Unity should really just delete it.

Right now I’m using static actions.

So, for example, you can have a script for GameObjects that, when picked up, add to the player’s score:

public class Collectible : MonoBehaviour
{
    [SerializeField] int points = 1;

    public static event Action<int, GameObject> OnCollected;

    // When I'm collected, I announce that fact to any listeners.
    public void Collected()
    {
        OnCollected?.Invoke(points, this.gameObject);
        Debug.Log($"{gameObject.name} has been collected!");
    }
}

I used an Action because it’s less verbose and, more importantly, it’s easier for me to see and understand which values it’s passing around when it announces/raises/publishes itself. Here, it passes an int and a GameObject.

I added the event keyword because… it makes it prettier in Visual Studio. Seriously, you don’t need to use the event keyword and it works just the same, but using it forces VS to apply its syntax highlighting for delegates. I’d love if someone more knowledgeable could weigh in on any pros/cons to this.

I made it static to allow anyone to subscribe without worrying about whether any of these Collectible game objects have actually been instantiated yet.

And I use the ?.Invoke null-conditional operator because it protects from a null reference error. Without this, an error would be produced if this was invoked but no other objects had subscribed. With this operator, however, it simply skips over if it would receive a null value.

Here’s the Score class that subscribes to and listens for the OnCollected action, and adds to the player score when that action has been invoked:

public class Score : MonoBehaviour
{
    int score = 0;
    TextMeshProUGUI scoreText;

    private void Start()
    {
        scoreText = GetComponent<TextMeshProUGUI>();
        UpdateScoreText();

        Collectible.OnCollected += AddToScore; // HERE WE SUBSCRIBE OUR ADDTOSCORE METHOD TO THE ONCOLLECTED ACTION
    }

    private void OnDisable()
    {
        Collectible.OnCollected -= AddToScore; // VERY IMPORTANT, HERE WE UNSUBSCRIBE!
    }

    void AddToScore(int amount, GameObject go) // THIS METHOD IS AUTOMATICALLY TRIGGERED WHEN THE COLLECTIBLE OBJECT INVOKES ONCOLLECTED
    {
        score += amount;
        UpdateScoreText();
    }

    void UpdateScoreText()
    {
        scoreText.text = score.ToString();
    }
}

(Although I pass around a GameObject with the event, it’s not used here by the Score class. I could add code to make Score keep track of which GOs have been collected or just use it with a different subscriber that might care about which GO has been collected but not about the points value.)

Oh, and it’s really important to remember to unsubscribe on OnDisable(). I ran into an annoying bug this way: I have the protagonist load on the title screen and then again in the game itself. I forgot to make the protagonist unsubscribe. What happened is the first-loaded protagonist subscribed, failed to unsubscribe, got destroyed when the game started, and then I got a null reference error when the action was invoked during the game.

Hey thanks for the info so far.
I have kinda answered my own question in a second post with links for future researchers about an hour later but it’s still “…awaiting moderator approval, and is invisible to normal visitors.” >.<

This! Basically the gist of what I found upon further research mentioned above, If/when my post with the links becomes visible this is also a very goof reference for people searching and stumbling upon this.
Thank you very much!
Now, I just have to work through the techniques to build a solid game manager with Scriptable Objects, maybe a topic for a future question post :wink:

1 Like