Simple Observer Pattern in Unity using C#

Sometimes, you want a change in one area of the code to propagate to other areas of the code. However, you don't always know where you want it to propagate to ahead of time. That's where the Observer Pattern comes in!

Basically what you'll do is set up a notification system within the original object. This original object is called the "Subject". When something changes in the Subject, it will notify anything that is observing it. These observing entities are called "Observers". It is up to the Observers to choose to observe the Subject; the Subject doesn't care about them.

A good example of this is Score. The player collects an item and their score increases. What all will need to know about this increase? Perhaps the UI will, maybe the audio engine will, and you may want something to auto-save it to the harddrive. With the Observer pattern, you don't need to know everything ahead of time.


Setting Up The Subject

As stated above, the object doing the notifying is called the Subject. So in our case, the score is the Subject. Everything else is an Observer.

In our example, we are going to create a list of Actions (which are delegates with no parameters or return variables). Any Observer can then add themselves to this list of actions. Any time the score changes, it is going to call every Action on the list of Actions. This way, the Observers can come and go as they please and will be updated accordingly, without the Subject keeping track of them.

We create our list of Actions like so:

public static class GameScore {
  private static List<Action> _scoreActions;
  ...
}

And we also need a way for Observers to register to the score:

public static void RegisterForScoreChange(Action actionToRegister) {
  // Lazily instantiate the list, in case nothing ever registers
  if(_scoreActions == null) {
  _scoreActions = new List<Action>();
  }

  _scoreActions.Add(actionToRegister);
}

If an Observer ever needs to stop observing, we should handle that as well. For instance, if the Observer gets destroyed, we don't want the Subject calling a null function!

public static void DeregisterForScoreChange(Action actionToDeregister) {
  if(_scoreActions.Contains(actionToDeregister)) {
  _scoreActions.Remove(actionToDeregister);
  }
}

And lastly, we want to create a separate function for the Score to call all its registered Observers' functions.

private static void CallRegisteredScoreFunctions() {
  if(_scoreActions != null) {
  foreach(Action action in _scoreActions) {
  action();
  }
  }
}

Now any time the Score updates, it can just call "CallRegisteredScoreFunctions()" and all Observers will be notified.


Setting Up The Observer

Now that the Score is ready to accept Observers, we need to create one. All an Observer needs to do is create a function with whatever logic it needs, then register that function with the Subject.

Note that because we are using a list of Actions, the Observers' functions need to have 0 parameters and 0 return variables. There are ways around this (aren't there always?) but this is the simplest approach.

For our example, we will create a basic UI Observer. It will create a function to update the score, register that function when it first gets initialized, and then display the updated score. Simple!

First, we want to create the function to update the score:

public void UpdateScore() {
  _oldScore = _score;
  _newScore = GameScore.CurrentScore;
}

Next, in Start(), which is called when the object is first initialized and enabled, we are going to register our function with the Subject.

void Start() {
  GameScore.RegisterForScoreChange(UpdateScore);
}

Now, any time the Score updates, it will loop through its lists of Actions and call our UpdateScore() function. To demonstrate this, we can output the score to a simple GUI.

void OnGUI() {
  GUI.Label(new Rect(0, 0, 100, 100), _oldScore + " -> " + _newScore);
}

And lastly, we want to make sure to deregister our function if we ever get destroyed. That way, the Subject doesn't make an unnecessary call. Or worse, throw a null reference error!

void OnDestroy() {
  GameScore.DeregisterForScoreChange(UpdateScore);
}

Conclusion

That's all! You can now repeat this process as much as necessary to hook up other areas of code. As mentioned before, perhaps you want to play an audio clip every time the score changes. Or you want to save the updated score elsewhere. You now have the flexibility to do so without touching the Score code (and potentially introducing a bug)!

If you have any questions or comments, or want to suggest a better way of doing things, feel free to leave a comment below!