Design Patterns: Observer Pattern for Game Developers

From traditional Software development, we have multiple design patterns at our disposal. However, they must go hand in hand with clean code and QA methods to be used effectively, otherwise they can turn into anti-patterns, hindering you instead. In this article we will take a closer look at the Observer Pattern.

What is the Observer Pattern?

To create an observer pattern, two roles must be implemented: a subscriber and a provider. The subscriber is also called a listener in some frameworks. Internally, the provider maintains a list of subscribers and notifies them when specific events or state changes inside the object happen. Essentially, this is an event-driven approach and this pattern is also used across multiple systems, in event-driven architecture. This pattern is a real all rounder and actually covers most use cases for game developers.

I’ve used this pattern extensively in game development context and found it extremely handy. It may look bulky at first, but in the long-term the pattern allows for great flexibility as it is like a generic container for logic that I can group together into handy chunks and reuse as my code grows.

How to Implement

If you can create a state machine graph out of a use case, you can an turn it into an observer pattern, where each transition is an event and each node is a provider.

When using a high-level object oriented language or technology (e.g. Java, C++, C#, Unity, Unreal) implement subscribers as interfaces and create in-line classes for them.


Restricted Section of ~484 Words

You must be logged in to view this content.

Usage

When to use

  • You know exactly that the code you’re trying to implement is not going to grow much or is less likely to grow in foreseeable future
  • reacting to damage or physics collision
  • reacting to enemy or player death
  • adding sound to certain events
  • entering an area in a world
  • mission triggers
  • events on clicking on a button
  • forwarding changes to UI/HUD
  • and many, many more…

When not to use

  • frequent updates that belong in the game loop
  • you do expect the affected code is expected to grow and needs to be flexible
  • do not use if complex logic across multiple system is involved (although I’d argue you might have some internal quality issues if that is the case)

Tipps

  • Do not pack very complex logic into the subscribers. You want to react to simple events, not add more complexity! If you’re adding too much logic to your events, take a few steps back and contemplate if you’re not unknowingly turning this pattern into an anti-pattern
  • The scope of each subscriber interface should be small, with just 1-4 methods at most. You can group these interfaces into a single class (one class can inherit from many interfaces) as required, and use them inside the provider lists instead! Below is some production code from AstraX that does this exact thing.
  • Avoid creating long event chains, ideally stick to 1 to 2. I never tried going beyond 3 but I can imagine that it must be a nightmare to debug

Leave a Reply