Learning Observer Pattern
In this post, I will explain how I applied the Observer Pattern in my Unity 2D Platformer Project.
What is the observer pattern? Let's suppose we have a collectible, and every time our player collides with that collectible, we want to update our UI. We could explicitly mention the UI in our code, like this:
- [SerializedField]
- UIManager ui;
Alternatively, we could use another design pattern, the singleton:
- UIManager.instance.UpdateCollectableQty();
It may sound like a good idea to always use the singleton, but as the game gets bigger, the use of the observer pattern turns out to be the best option.
- public static Action <CollectablesNames, int> collectedAction;
- collectedAction?.Invoke(collectableName, qty)
The idea here is that every time our player collides with the collectible, we are going to invoke an action. In this way, we don't need to specify which objects we want to notify when we execute this function. But how can they know then?
In general, there should be two things: the subject and the observer. In this case, our Collectable Manager class is the observer (that's why it's listening to the function UpdateCollectableQty), and our subject is the collision. When that subject is called, our listeners will take a specific action that the subject does not have control of.
Ok, but what's the advantage? Imagine that when the collision happened, we had to these things:
- Play a sound
- Update the UI
- Update the collectable manager
In the singleton pattern, I would have to tell specifically which objects and which function I should call. For example:
- soundManager.instance.PlayCollectSound(sound)
- uiManager.instance.UpdateCollectable(data)
- collectableManager.Update(data)
So, the control stays all in our subject. That's not good practice! It should stay in our observer: collectedAction?.Invoke() That's it! And then all the different behavior will be controlled in the listeners.
Note: The action also sends a parameter, and the function attached to it must have the same parameters; otherwise, it won't work.
This solution made the project much better. Here are a few advantages:
- Before, each collectible had its own audio player. Now I have one audio player that observes many functions, and then, depending on the parameter of the function, it is going to play a different audio.
- On the player, I had multiple audios as well. For example, when I collided with the checkpoint, the checkpoint would call a function in my player to play that specific audio. Now, I just call the sound manager when the collision happens!
- My scripts are not dependent on each other now! I can create more scripts and actions more easily without having to worry about all the connections.
Final example:
private void OnTriggerEnter2D(Collider2D collision)
{
EnterLevelUI.instance.SetLevelText("Level " + (levelIndex + 1)); // We have to tell the class and the function
playerEnteredDoor?.Invoke(levelIndex + 1); // We only tell the action; the function and the class are controlled independently
}
private void OnTriggerExit2D(Collider2D collision)
{
EnterLevelUI.instance.SetLevelText("");
playerEnteredDoor?.Invoke(-1);
}
Get The New Eye
The New Eye
2D Platformer with multiple challenges
Status | In development |
Author | ginocarlo01 |
Genre | Platformer |
Tags | 2D, Casual, Pixel Art, Singleplayer, Unity |
Leave a comment
Log in with itch.io to leave a comment.