Using the Observer Pattern to improve your system modularity.
Published 2 years ago
Objects Communication with Delegates, Events and Properties
When creating new components for you project they rarely don't need to communicate with other parts of your system. Object communication is simplified in Unity by the usage of the Editor and its drag and drop capabilities. Although just creating a public reference on your component and using the editor to link it can be practical, sometimes isn't the best solution.
Using the Unity editor to link objects decrease the modularity of your Game Objects by causing dependency between them. It's often the case that you'll have a Player object and you need to test some brand new functionality.
So you create a test Scene and drag the Player Prefab to that scene. When you hit Play you receive an error saying that something like UI Object reference not set to an instance of an object. You then get your UI Prefab and drag it into the Scene and you repeat that until you have no errors. Suddenly you have a complete Scene just for testing your Player Prefab.
One solution for this kind of problem is using c# implementation of the Observer Pattern with Delegates, Events and Properties. There are several others solutions, I'm not claiming this is the best, it is just a quick way to improve your System Architecture Design. And I'll not explain extensively here the terms like Observer Pattern and how to use every functionality of Events (there are plenty good material on the web), I'll offer a general approach on how to use it on Unity.
So let's consider the simple project, you have a Player with a hp attribute and you have an UI that you want to display the Player's hp.
A simple way to implement it is the one we discusses above: have the Player object hold a reference to your UIManger:
public class Player : MonoBehaviour { public int hp; public UIManager uiManager; public void TakeDamage(int damage) { hp -= damage; uiManager.UpdateHPText(hp); } }
Now this is kind of bad, because what if we needed to test our new functionality and it had something to do with our player taking damage? An error would be raised if the UIManager wasn't on the Scene. To prevent that we'll use the power of Events to improve our code.
We'll chance our design and apply the Observer Pattern. Instead of the Player object holding an reference to the UIManager, he will simply emit an Event and the UI will subscribe to that Event.
That means that when the Player takes any damage he will tell everyone that is listening that an Take Damage Event has just occurred. If the UI is instantiated, it'll listen to the Take Damage Event and will update the UI accordingly. If it isn't, the Player will just say he took damage and no one will listen (and consequently will not raise any errors).
First let's re-write our Player Code.
public class Player : MonoBehaviour { int hp; public delegate void PlayerTookDamageHandler(int remaningLives); // Event that is raised when the player takes damage public static event PlayerTookDamageHandler PlayerTookDamage = delegate { }; public int HP { get { return hp; } set { hp = value; PlayerTookDamage(hp); } } public void TakeDamage(int damage) { HP -= damage; } }
Taking a look on the re-written code we can see various changes, but the most important is there is no reference to no UIManager. We instead have an event that is raised whenever the hp value is changed. We enforce this behavior by accessing the variable trough a property instead by accessing it directly.
Now we have the Player Object telling everyone when he takes damage, but we haven't programmed no one to listen to it. So let's add a subscriber to that Event.
public class UIManager : MonoBehaviour { private void Awake() { Player.PlayerTookDamage += UpdateHPText; } private void OnDisable() { Player.PlayerTookDamage -= UpdateHPText; } public void UpdateHPText(int currentHp) { // Code to update the UI } }
When we write "Player.PlayerTookDamage += UpdateHPText;" we are telling C# to execute the function UpdateHPText when the PlayerTookDamage event happens.
With the new code we have the UIManager and the Player objects completely independent objects and they can be instantiated on their own.
This example was using a Player and an UI, but it can be expanded to any communication between objects. Using this approach instead of linking objects will create a way more organized system. I suggest if you have never used events to really give it a try, it'll greatly improve your code and the way you build your system.
Pedro Azevedo
Indie Developer - Programmer