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.