Dev Blog #2: Game states

Every game has numerous screens the user passes before he or she can start playing. Regularly, the list of screens consists of splash screens, loading screens, main menu screens and so forth. Each of those steps can be viewed as a separate state of the application. This is the exact case in CuCumbersome Life.

Every state of the game is represented by the interface IGameState. A game state handles all its game objects, including updating and drawing.

public interface IGameState
{
  List KeyCommands { get; }
  ICollection GameObjects { get; }

  void AddObjects(params IGameObject[] gameObjects);
  void Update(GameTime gameTime);
  void DrawRelativeObjects(SpriteBatch spriteBatch, GameTime gameTime);
  void DrawAbsoluteObjects(SpriteBatch spriteBatch, GameTime gameTime);

  void BuildUp();
  void TearDown();

  IEnumerable GetGameObjectsOfType()
    where TGameObject : IGameObject;
}

The reason this interface has two different draw methods is, that in this game, every object that should be drawn with a (camera) translation matrix is drawn in DrawRelativeObjects(). Static content like menus and overlays are drawn within DrawAbsoluteObjects().

Game state handler

The magic happens in a class I called GameStateHandler. It basically controls the current state and takes care of all the other states as well. Additionally, it is the central place where the game should transition from one state to another. This is the code:

public static class GameStateHandler
{
  private static readonly IDictionary RegisteredGameStates = new Dictionary();

  private static GameState _currentGameState;

  public static GameState CurrentGameState => _currentGameState ?? throw new ArgumentNullException("No GameState was registered.");

  private static bool _isGamePaused = false;
  public static bool IsGamePaused
    {
      get => _isGamePaused;
      set
      {
        _isGamePaused = value;
      if (value)
      {
        OnGamePaused(null, new GamePausedOrResumedEventArgs("User paused game"));
      }
      else
      {
        OnGameResumed(null, new GamePausedOrResumedEventArgs("User resumed game"));
      }
    }
  }

  public static EventHandler GameStateChanged { get; set; } = (sender, args) => { };
  public static EventHandler OnGamePaused { get; set; } = (sender, args) => { };
  public static EventHandler OnGameResumed { get; set; } = (sender, args) => { };

  public static void RegisterGameState(GameState gameState)
  {
    if (gameState == null)
    {
      throw new ArgumentNullException(nameof(gameState));
    }

    if (!RegisteredGameStates.Any())
    {
      _currentGameState = gameState;
      _currentGameState.BuildUp();
    }

    RegisteredGameStates.Add(gameState.GetType(), gameState);
  }

  public static void TransitionTo(bool forceTransition = false)
    where TType : IGameState
  {
    Type newGameState = typeof(TType);

    if (CurrentGameState.GetType() == newGameState && !forceTransition)
    {
    return;
    }

    if (!CurrentGameState.CanTransitionTo(newGameState) && !forceTransition)
    {
      throw new ArgumentException($"Cannot transition from '{CurrentGameState.GetType()}' to '{newGameState}'.");
    }

    if (!RegisteredGameStates.ContainsKey(newGameState))
    {
      throw new ArgumentException($"No GameState registered for '{newGameState}'.");
    }

    GameStateChanged.Invoke(null, new GameStateChangedEventArgs(CurrentGameState.GetType(), newGameState));

    _currentGameState.TearDown();

    _currentGameState = RegisteredGameStates[newGameState];

    _currentGameState.BuildUp();
  }
}

Okay, it appears to be a wall of code, but if you skip the argument null checks and ignore the comments (which I had to remove due to formatting errors), it actually comes down to a simple construct.

One thing that I really like about this setup is, that for each game state, you can define which other states it can transition to. Check out the MainMenuGameState, for example:

[CanTransitionTo(typeof(GameplayGameState))]
[CanTransitionTo(typeof(SettingsMenuGameState))]
public class MainMenuGameState : GameState

This way you can easily configure the flow of the game or maybe even generate a diagram based on this structure.

The point of showcasing how my game handles states is to foreground that a good structural setup of the core of your game is essential for expandability.
With a construct like this GameStateHandler, it is easy to add a new game state and changing the core code is not required.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s