Game Design 2 - State Manager

Code structure while working on our termination conditions using a state manager and a notification center.

Written the by Thomas Cairns.

Introduction

This post is part of a series. Previous post Game Design 2 - Bounce Mechanic

Task

One of my task since the last post has been to work on implementing the termination conditions of our game and displaying the appropriate screen for each condition. What this entails is managing states of the game. A state within the context of a game is usually seen as a screen or a mode where you interact with the game in a different way. Examples of different states would be, playing the game, pausing the game or when the game over screen is presented.

As a very late goal for the project, as part of polish, we would like to have a start menu that displays the playing field in the background with the player avatar and enemies moving around idly. When the player decides to start the game, the avatar and enemies should move smoothly into their start positions creating a smooth transition into the playing state. The feature would be quite an undertaking and nothing that we are focusing on right now. However we want to implement a simpler version where we keep the background moving smoothly across states.

State Manager

Within Unity the Scene system and object naturally encapsulates the idea of states. There also exists an object that allows for management of the Scenes called the SceneManager. However based on what I have gathered the SceneManager only allows for one Scene to be loaded at a time. For our feature to work as intended we need multiple states to be loaded at the same time in order to create smooth transitions and keeping some objects constantly alive so the scene system wouldn't work for this. The scene system is probably a lot more flexible then what I give it credit, but when no obvious solution shows up when search the documentation and forums I have to keep working with the knowledge I have. I'm sure I will be proven wrong at a later date when I have more experience with Unity.

As an alternative to the SceneManager I have created a manager called well the StateManager. The StateManager has a static list of GameObject's where each object represents a state. The manager takes care of switching (activating/deactivating) and overlaying states, and exposes methods that allows for different components to trigger change.

The solution works fairly well except for resetting the playing state where we have to reload the entire main scene which negates the effect that we want to achieve. Hopefully I will find a solution that is not to complicated.

Notification Center aka Observer Pattern

A common programming problem for games is that a lot of components should take action when something happens but only one component know when it's time to take said action. For example the avatar knows how many lives it has and when it has reach its "death" and the death should trigger the game over menu to be displayed, which was the job of the state manager. The obvious solution is to have the state manager know about the avatar. The problem with this solution wont be noticed immediate but as we expand the game with features more and more components will have to know about each other, this turns in to a code maintenance nightmare after a while.

Many programming problems, like the one above, are old and common and have over the years been solved in multiple ways, the method to solve them are usually called patterns within the development community. Of course this one is no exception, one solution is to use the observer pattern. Simplified the observer pattern work in such a way that it creates a hub where components can register that they want to be notified then a specific action occurs. The component that knows when an action has occurred tells the hub to send a notification to everybody that has registered. This way each component only needs to know about the hub and the hub takes care of the rest.

Notification center registration

How the registration takes place in relation to the notification system. Only the object that wants to take action needs to registers.

Post to the notification center

How the avatar posts to the state manager and then notifies anybody who has registered to the specific event.

Code

The following code is the notification center aka the hub described above.

CS_Notifications.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class CS_Notifications : CS_Singleton<CS_Notifications> {
  // Hide constructor for the singleton pattern.
  protected CS_Notifications() { }

  private Dictionary<string, HashSet<Component>> notifications;

  private void Awake()
  {
    notifications = new Dictionary<string, HashSet<Component>>();
  }

  public void Register(Component observer, string method)
  {
    if (!notifications.ContainsKey(method))
    {
      notifications.Add(method, new HashSet<Component>());
    }

    notifications[method].Add(observer);
  }

  public void Unregister(Component observer, string method)
  {
    if (!notifications.ContainsKey(method))
    {
      return;
    }

    notifications[method].Remove(observer);
  }

  public void Post(Component sender, string method, Hashtable data)
  {
    foreach (Component observer in notifications[method])
    {
      observer.SendMessage(method, data, SendMessageOptions.DontRequireReceiver);
    }
  }
}

The following code shows how a component register/unregister themselves from the notifications center. The second parameter is the specific event that the component want to know about, for the state manager this would be OnVictory and OnGameOver. Then you simply implement a method with the exact same name and this method will be trigger by the notification center.

CS_StateManager.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;

public class CS_StateManager : MonoBehaviour {

  private void Awake()
  {
    CS_Notifications.Instance.Register(this, "OnVictory");
    CS_Notifications.Instance.Register(this, "OnGameOver");
  }

  private void OnDestroy()
  {
    CS_Notifications.Instance.Unregister(this, "OnVictory");
    CS_Notifications.Instance.Unregister(this, "OnGameOver");
  }

  private void OnVictory()
  {
    // Code to run when the victory condition is meet.
  }

  private void OnGameOver()
  {
    // Code to run when the victory condition is meet.
  }
}

The following demonstrates how to trigger an event.

CS_Avatar_Health.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class CS_Avatar_Health : MonoBehaviour {

  void OnCollisionEnter2D(Collision2D collision)
  {
    // When the avatar reaches zero health.
    CS_Notifications.Instance.Post(this, "OnGameOver", null);
  }
}

Singleton Pattern

The notification center is also implemented with the singleton pattern. The idea with a singleton is that there is only one instance of and component running at once and it can be access anywhere within the code base. This greatly increases the ease-of-use of the notification center as demonstrated above. It's worth noting that the use of the singleton pattern is often discouraged because it has been extremely abused by developers in the past but as with most thing if used sparingly and with forethought it can solve specific problems. The code used can be found at the Unity wiki.

CS_Singleton.css

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

/// Credit: http://wiki.unity3d.com/index.php/Singleton
/// 
/// Be aware this will not prevent a non singleton constructor
///   such as `T myT = new T();`
/// To prevent that, add `protected T () {}` to your singleton class.
/// 
/// As a note, this is made as MonoBehaviour because we need Coroutines.

public class CS_Singleton<T> : MonoBehaviour where T : MonoBehaviour
{
  private static T _instance;

  private static object _lock = new object();

  public static T Instance
  {
    get
    {
      if (applicationIsQuitting)
      {
        Debug.LogWarning("[Singleton] Instance '" + typeof(T) +
          "' already destroyed on application quit." +
          " Won't create again - returning null.");
        return null;
      }

      lock (_lock)
      {
        if (_instance == null)
        {
          _instance = (T)FindObjectOfType(typeof(T));

          if (FindObjectsOfType(typeof(T)).Length > 1)
          {
            Debug.LogError("[Singleton] Something went really wrong " +
              " - there should never be more than 1 singleton!" +
              " Reopening the scene might fix it.");
            return _instance;
          }

          if (_instance == null)
          {
            GameObject singleton = new GameObject();
            _instance = singleton.AddComponent<T>();
            singleton.name = "(singleton) " + typeof(T).ToString();

            DontDestroyOnLoad(singleton);

            Debug.Log("[Singleton] An instance of " + typeof(T) +
              " is needed in the scene, so '" + singleton +
              "' was created with DontDestroyOnLoad.");
          }
          else
          {
            Debug.Log("[Singleton] Using instance already created: " +
              _instance.gameObject.name);
          }
        }

        return _instance;
      }
    }
  }

  private static bool applicationIsQuitting = false;

  /// When Unity quits, it destroys objects in a random order.
  /// In principle, a Singleton is only destroyed when application quits.
  /// If any script calls Instance after it have been destroyed,
  /// it will create a buggy ghost object that will stay on the Editor scene
  /// even after stopping playing the Application. Really bad!
  /// So, this was made to be sure we're not creating that buggy ghost object.

  public void OnDestroy()
  {
    applicationIsQuitting = true;
  }
}