Thursday, May 1, 2014

A Simple State Pattern Applied to Game Design

When it comes writing software, less is better, generally speaking. The fewer lines of code you have, the lower the chance of doing something wrong. For me, this is most obvious when it comes to the number of if-statements in my code.

Here's an idea for reducing the number of if-statements used to transition between game states. It's not a new idea. In fact, it's an old design pattern from the GoF Design Patterns book.

Toggalight, my second game, is small in scope, but has the usual need of presenting different UI elements, and responding to user interaction differently depending on whether the game is running, paused, or over.

When I started on this project, I decided I wasn't going to stick myself into an if-statement quagmire again. So I broke out the trusty State pattern and went to work.

Overview

The game has four buttons that cause state changes: Start, Pause, Resume, Start Over. The GameScene class implements a library of functionality that is available to both the view controller and the various state classes.

Note: I'm using a python-like pseudo-code, and will be abstracting most of the details so we can focus on the state structure and transitions. The game is actually written for iOS in Objective-C, using SpriteKit.

BaseGameScene

This is the base class for all game states, and defines the interface expected by the GameScene.

abstract class SceneState
  property gameScene

  // state management - empty base class methods that don't do anything
  method enterState()
  method exitState()

  // Events - empty base class methods that don't do anything
  method onFrameUpdate()
  method onLightTouched()

  method onStartButton()
  method onPauseButton()
  method onResumeButton()
  method onStartOverButton()


RunningState

I'll spare you the details of all the states, but here's the gist of what's in the most complex state, the one that's active while the game is being played.

class RunningState : SceneState

  method enterState()

    // set up UI for state
    gameScene.pauseButton.show()
    gameScene.startButton.hide()
    gameScene.startOverButton.hide()
    gameScene.resumeButton.hide()
    gameScene.helpButton.hide()
    gameScene.calibrateButton.hide()
    gameScene.highScoreLabel.hide()

    // Physics are different between running and game over states
    gameScene.setGameRunningPhysics()

    // If this is a new game (not a resume), layout a new field
    // and reset the clock.  Yes, I know it's a nasty if statement.
    // I'll probably refactor this in the next release.
    if (gameScene.shouldReset)
       gameScene.generateNewField()

    // Unpause the scene
    gameScene.frozen = false


  method exitState()
    // Do nothing. The base class defines an empty method for this

  method onFrameUpdate()
    // Respond to device motion (accelerometer and gyroscope)
    motion = os.getCurrentMotion()
    gameScene.physics.gravity = Vector(motion.roll, motion.pitch)

    // Check time remaining
    // Yup, more refactoring needed here, too. onClockExpired would be
    // a good event to handle at the state level.
    if gameScene.clock.isExpired()
      gameScene.transitionToState(gameScene.gameOverState)

  method onLightTouched(light)
    // Toggle it off/on
    light.on = not light.on
    gameScene.updateScore()

    // If all the lights are turned on, layout a new field
    // This if-statement seems appropriate, actually.
    if light.container.allOn()
      gameScene.generateNewField()


  method onPauseButton()
    gameScene.transitionToState(gameScene.pausedState)


You get the idea. The other states follow the same pattern, doing what they're supposed to do when the events are triggered.

GameScene

Here are the relevant parts of the GameScene class.

class GameScene

  property preStartState
  property runningState
  property pausedState
  property gameOverState

  property currentState

  method init()
    // Lots of code to set up the scene.

    preStartState.gameState = self
    runningState.gameState = self
    pausedState.gameState = self
    gameOverState.gameState = self

    transitionToState(preStartState)

  method transitionToState(newState)
      // Objective-C lets me get away with not checking for 
      // null first. Lazy? yes.
      currentState.exitState()
      currentState = newState
      currentState.enterState()

  method onStartButton()
    currentState.onStartButton()

  method onStartOverButton()
    currentState.onStartOverButton()

  method onPauseButton()
    currentState.onPauseButton()

  method onResumeButton()
    currentState.onResumeButton()

  // This method is called by the "os"
  method frameUpdate()
    currentState.onFrameUpdate()

There you have it. PreStartState goes to RunningState. RunningState goes to PausedState and GameOverState. PausedState goes to RunningState, setting the shouldReset flag or not depending on whether the Resume or Start Over button was pushed. GameOverState goes to RunningState.

Each state sets up the UI according to its context. In the case of this game on iOS, touch events are translated by the OS into the "onButton" events in the view controller, which delegates the events to the GameScene, which in turn delegates them to the current SceneState, which is where the knowledge of what to do is programmed.

That's the essence of the State design pattern applied to my game architecture. It's not necessarily revolutionary, but it's made a world of difference in the way I track down problems and solve them.