Watch out, you’re being observed!

Hi! Welcome in the essentially first post on Games Architecture. I’m really glad you’re here and just in a minute I’ll let you dive into the substance, but first you need to know something about me. I’ve mentioned it in the very first post, but I think it is quite important to point it out once again. I try to simplify things wherever I can and the code is first-class example. Programming is more about reading the code than writing it. You really want to make the first one as easy as you can, for future-you and the fellas that will be helping you.

There are a couple of places where you can find some information about this design pattern (and I’ll point you there at the right time) but we will focus on examples and some tangible aspects of it. Ok, so here’s the problem:

Let’s assume we have a monster and a player. Pretty common setting in games, isn’t it? The monster attacks the player, so programming it can be as simple as calling player.TakeDamage(). But (you expected the ‘but’ didn’t you?) the games are rather multimedia experiences, so you’ll probably want to have some audio (ouch!) and maybe a screen effect, on instance red Call of Duty style indicator. Oh, oh, and somewhere on the screen the HP bar is placed. Let’s write this:

public class Enemy : Actor
{
    private Player _player;

    private int _attackDamage = 10;

    private void Update()
    {
        _player.TakeDamage(_attackDamage);
    }
}
public class Player : Actor
{
    private AudioController _audioController;
    private Camera _effectsCamera;
    private UIController _uiController;

    private int _healthPoints = 100;

    public void TakeDamage(int damageValue)
    {
        _healthPoints -= damageValue;

        _audioController.PlayHitSound();
        _effectsCamera.ShowHitIndicator(seconds: 1);
        _uiController.UpdateHealthBar(newValue: _healthPoints);

        // Deal with players death...
    }
}

So the diagram looks like this:

It’s quite possible that you feel that this is somehow wrong. Besides fighting with monsters and caring about our input, the hero has to manage all of the things he shouldn’t know about. To keep abstraction cohesive, player should care about input and his behavior, monster about attacking, audio about playing… you get the idea. But how to call all of these methods without actually calling them from player class? The answer is – don’t call them. Just let the player shout about the fact that it hurts so badly and hope for the best. Or you can make sure that someone is hearing by programming it. The one who hears the player’s pain is our today’s star – The Observer:

But the Observer is not a separate class, it’s more like an additional ability or a role of the existing class. He (the Observer) can listen to the others (classes of interest – subjects) and based on that, rule his realm (call appropriate methods). Instead of the above scenario, when subjects come to the Observer and make him do things (“Hey ruler, play that track for me, mkay?”), they just do their job and when something interesting happens, like they get hit by an arrow in the knee, they just shout about it. And they don’t know who and how many of them are listening in the moment. It can be a Bard (Audio) Observer that plays a sad song about the fallen hero, or it can be a Painter (UI) Observer, that updates his paintings that portray hero’s condition. Or it can be both of them. Or none. Nothing changes for the hero. Snap back to the reality (Oh, there goes gravity) and write some code.

public class Player : Actor
{
    /// <summary>
    /// Sends players object that just have died.
    /// </summary>
    public Action<Player> Dead = delegate { }; // Don't be afraid of that syntax, its use is pretty simple.

    /// <summary>
    /// Sends players object that has been hit and new health value.
    /// </summary>
    public Action<Player, int> Hit = delegate { };

    private int _healthPoints = 100;
    
    public void TakeDamage(int damageValue)
    {
        _healthPoints -= damageValue;

        Hit(this, _healthPoints); // Shout about being hit.

        if (_healthPoints <= 0)
        {
            //Send another event, about players death and let the Gameplay Controller decide what to do with the body.
            Dead(this);
        }
    }
}
public class AudioController
{
    private Player _player;

    private void Start()
    {
        // Start listening.
        _player.Dead += OnPlayerDead;
        _player.Hit += OnPlayerHit;
    }

    private void OnPlayerDead(Player player)
    {
        // Stop listening.
        _player.Dead -= OnPlayerDead;
        _player.Hit -= OnPlayerHit;
    }

    private void OnPlayerHit(Player player, int newHealthValue)
    {
        PlayHitSound();
    }

    private void PlayHitSound()
    {
        // Ouch!
    }
}

We’ll skip the Camera and UIController scripts, they are analogous to the above.

NOTE I: I’ve introduced one more event, Dead, to show unsubscribing and some danger related to using events. I’ll make use of it later.

NOTE II: In the above example I’m sending player’s object. I didn’t have to do that, as every controller knows the player, but if we had a multiplayer game, we would want to know which player has been hit.

So now the diagram looks like that:

NOTE III: I dropped drawing arrows from the controllers to the player and replaced them with little antennas to keep diagrams more readable, but remember that now they are linked to the player.

Or, we can go for more centralized control:

public class GameplayController
{
    private AudioController _audioController;
    private Camera _effectsCamera;
    private Player _player;
    private UIController _uiController;
    
    private void Start()
    {
        // Start listening.
        _player.Dead += OnPlayerDead;
        _player.Hit += OnPlayerHit;
    }

    private void OnPlayerDead(Player player)
    {
        // Stop listening.
        _player.Dead -= OnPlayerDead;
        _player.Hit -= OnPlayerHit;

        // Restart level.
    }

    private void OnPlayerHit(Player player, int newHealthValue)
    {
        _audioController.PlayHitSound();
        _effectsCamera.ShowHitIndicator(seconds: 1);
        _uiController.UpdateHealthBar(newHealthValue);
    }
}

And the diagram:

The first one is simpler but less reusable as the controllers are more rigidly connected to player’s events. The second one allows for controllers reuse, as the Gameplay Controllers uses their API. Gameplay Controller in that case will be linked to everything, which is a bit harder to manage, but has an advantage in some cases. For example, if your game has a few game modes, changing game behavior is as simple as writing a new Gameplay Controller and switching between them accordingly. In that case making them derive from a common class will possibly be a good idea.

Okay, let’s point out the biggest advantages of using this pattern and then we will move to the another example. Events (this is what the Observer listens) wonderfully decouple classes, so one of them doesn’t have to be aware of the second’s existence. And what do we get thanks to that?

  • Adding another behaviors or changing existing ones doesn’t entail a change of all the other classes that are involved. Less opportunities to make a mistake.
  • It is easier to wrap your mind around programmed features, because you have to focus only on one class at the time. UI, Audio and Image effects can wait until you’re done with the most important code – players behavior.
  • Changing the Audio System completely, its API, adding and removing image effects (so classes from above*) doesn’t force you to change the player (class from below*). The new Audio System just listens to an appropriate event (but it doesn’t have to, if we don’t want to play that sound anymore) and does whatever it does. Player doesn’t know about it and doesn’t even care. The only thing that can change is adding a new event. And it can be used by every other class, without linking the player to anything.

* Not-so-formal definition of what I mean by classes from above and from below sounds as follow: Class from above knows more about game’s realm comparing to class from below. For example, player is class from above for projectile class. The second one may know about itself and about physics engine, while the first one knows about itself and that projectile. It doesn’t mean that class from above has to have reference to the class from below. It is just an ability to know.

Let’s jump to the another example.

Imagine a level divided into a hex fields and two armies confronting each other on it. There are a couple of obstacles on some fields. Some of the hexes are placed on the hill, what gives the accuracy boost to the unit that stands there. When the soldier misses his shot, the opposite one has a chance to lead a counterattack. OK, let’s stop here. If you played something like Heroes or Neuroshima Hex, you are probably aware, how complex these systems can be. You can exercise a little and before you start looking closer on the code and the diagram below, you can try to solve this on your own. Oh, hey, you can even share it with me in the comments if you like! When you are ready, there is one of the solutions**:


The diagram is quite simple, right? I think that we need some code snippets:

public class Unit
{
    public Action<Unit> AnimationDodgeEnd = delegate { };
    public Action<Unit> Dead = delegate { };
    public Action<Unit, int> Hit = delegate { };

    private int _accuracy = 50;
    private int _damage = 50;
    private int _range = 3;
    private Player _owner;
    private Vector2 _currentPosition;

    public Unit(Player owner, int accuracy, int damage, int range) { /* Initialize private fields. */ }

    // Getters for private fields.

    public void Move(Vector2 newPosition) { /* Move the unit to the new position. */ }

    public void TakeDamage(int damageValue) { /* Pretty same as in player class. */ }

    public void PlayAnimationAttack() { /* ... */ }

    public void PlayAnimationDodge() { /* ... */ }

    public void PlayAnimationHit() { /* ... */ }
}
public class EnvironmentController
{
    public int GetFieldAccuracyBonus(Vector2 fieldPosition) { /* Check field bonus and return it. */ }
    
    public bool IsFieldAvailable(Vector2 fieldPosition) { /* Check field availability and return it. */ }

    public bool IsFieldInRange(Vector2 centerField, int range) { /* ... */ }
    
    public void HighlightFieldsAround(Vector2 centerField, int range) { /* Highlight available fields on green and unavailable on red. */ }
}
public class FieldInputController
{
    public Action<Vector2> FieldClicked = delegate { };
    public Action<Unit> UnitClicked = delegate { };
}
public class BattleController
{
    private EnvironmentController _environmentController;
    private FieldInputController _fieldInputController;
    private Player _currentPlayer;
    private Unit _activeUnit;

    private void StartBattle()
    {
        _fieldInputController.FieldClicked += OnFieldClicked;
        _fieldInputController.UnitClicked += OnUnitClicked;
    }

    private bool IsAttackHit(Unit attackerUnit, int additionalAccuracy) { /* ... */ }

    private void OnFieldClicked(Vector2 fieldPosition)
    {
        // If player clicked on field, move the player there if it is available for him.
    }

    private void OnUnitClicked(Unit clickedUnit)
    {
        // If player clicked on his unit, make it active and highlight available fields around it.
        Player unitOwner = clickedUnit.GetOwner();
        if (unitOwner == _currentPlayer)
        {
            /* ... */
        }
        else // If player clicked on the enemy, attack that enemy unit if it's in range.
        {
            bool playerHit = TryAttackUnitInRange(_activeUnit, clickedUnit); // Plays the attackers attack animation.
            if (playerHit)
            {
                clickedUnit.PlayAnimationHit();
            }
            else
            {
                clickedUnit.PlayAnimationDodge();
                clickedUnit.AnimationDodgeEnd += OnUnitDodgeEnd;

                // Stop listening to input, until the turn ends.
                _fieldInputController.FieldClicked -= OnFieldClicked;
                _fieldInputController.UnitClicked -= OnUnitClicked;
            }
        }
    }

    private void OnUnitDodgeEnd(Unit victimUnit)
    {
        // Oh, yeah, we don't need to listen this anymore.
        victimUnit.AnimationDodgeEnd -= OnUnitDodgeEnd;

        // Give it back!
        bool unitHit = TryAttackUnitInRange(victimUnit, _activeUnit);
        if (unitHit)
        {
            _activeUnit.PlayAnimationHit();
        }
        else
        {
            _activeUnit.PlayAnimationDodge();
        }

        // Switch the current player, end the turn and listen to the input again.
        // We don't need to hear to attackers dodge animation end,
        // because we're assuming here, that end turn animation will last longer.
        // In the actual game you shouldn't rely on animations duration and another event subscription would be needed.

        _fieldInputController.FieldClicked += OnFieldClicked;
        _fieldInputController.UnitClicked += OnUnitClicked;
    }

    private bool TryAttackUnitInRange(Unit attackerUnit, Unit victimUnit) { /* Returns true, if attacker unit hit the victim unit. */ }
}

** I said ‘one of the solutions’, so can there be more than one? Is that at least the best one? Answer for the first question is yes and for the second one is probably no. It is really good though and easy to understand as this pattern is well known by the programming world. You should always look for the better solution, when you can see flaws in the current one that cannot be accepted. Don’t look for the perfect architecture, because there rarely is one. You should choose a solution that is good enough, be aware of its advantages and disadvantages and be confident that despite the fact that the cons exist, this is the right, conscious choice. And just for the record, we can talk about conscious choice only when you considered more than one solution.

There you go. Use cases for Observer pattern are countless, but don’t get too excited. There are some flaws. You can find more technical one (memory and performance) here and I’ll add one more, from the programmer point of view. There are difficulties related to where to start subscribing and where to stop it. In the ideal world, we could hook up to the events on the application start and don’t bother unsubscribing. Or we could do it on level start and level end. But we live in a more challenging world, so for example we’re spawning (or enabling in case of object pooling) objects – monsters, projectiles, map chunks, menu entries and we’re destroying (disabling) them eventually. That’s why we have to spend additional time to check if we have subscribed as many times as we have unsubscribed. And it’s not always that obvious because of the complexity of the code. Consequences of this are very different, they are often hard to find and usually barely visible. The easiest case is when you can see that for one tango down you gain points two times. But it is rarely that easy. That’s why you should take a breath, take some distance (both literally and metaphorically) and double check if everything is fine.

In first example we began listening in Start method and stopped when the player was dead. To make it work, after player’s death we should restart whole level, so the Start method will be called. When we are just spawning the player (with blinking and immortality?) short after his death, we won’t subscribe to his events.

This inconvenience shouldn’t stop you from using the events, because at the end of the day they are crucial in game development. You’ll make mistakes, everyone does – especially if this is new to you. But the fear of unknown shouldn’t be the deciding factor in not using them. Go for it!

Okay, we made it. Despite the topic isn’t new, there is still so much codebases that don’t utilize the Observer pattern. That’s why I decided to start with it, even if it might be not the strongest card I could draw in the beginning. As you can see, the blog is new, so every feedback will be very appreciated. Tell me what you think, what are your experiences with the topic, who you are. If we’re sharing the same passion, I want to know.

I’ve got a bunch of notes and drafts for the next posts and I’m excited about sharing them with you. If you think that it might be somehow interesting, stay in touch with me.

Oh, oh! I hope you’ve seen the first “hello world” post about the blog and its future content. It will give you a better insight in what will be happening here. See you soon!

Share these goodies:

3
Leave a Reply

avatar
1 Comment threads
2 Thread replies
32 Followers
 
Most reacted comment
Hottest comment thread
2 Comment authors
BartoszJediPanda Recent comment authors
  Subscribe  
newest oldest most voted
Notify of
JediPanda
Guest

Glad to say that I’ve read this fully and even understand the concept, probably because I’m not that unfamiliar with it, however, I never had used it until now, probably because I haven’t really understood it, but after reading this, I would definitely use it.

I do have a question, (probably you mention it above in the article “You can find more technical one (memory and performance) here and I’ll add one more, from the programmer point of view”).
How many observers are ok in a game so that it won’t affect the performance or the complexity of the game?

I mean I can see myself using it in a lot of cases, for scoring, levels, enemies, maybe for tracking different events in the game and so on, is it to much If I choose to use it for all the game events, would it be wrong? Would it be hard to manage the complexity? Would this affect the performance of the game in a bad way?