Writing the state machine

In the few games that I have written I have always had something called the State Machine that handled what was being executed during the main loop. I am unsure where I first picked up this method but it has the following simple interface:

interface StateMachineI {
    Push (s *State)
    Pop (s *State) error
    Replace (s *State) error
    Peek () *State, error
}

type StateMachine struct {
    stack []*State
}

With State being just as simple:

interface State {
    Init (owner StateMachine)
    Update (dt float32)
    Draw (dt float32)
    Destroy (owner StateMachine)
}

When a State is added to the state stack via either Push or Replace its Init function is called. Similarly if its removed via either being replaced or being removed from the head via Pop the states Destroy function is called. This allows each state to manage its dependencies and clean up if needed.

Then within the main function I am able to set up the state engine like so:

func main() {
   sM := StateMachine{stack: make([]*State, 0)}
   sM.Push(NewWelcomeState())

   var dt float32

   for {
       if currentState, e := sM.Peek(); e != nil {
           // You can imagine what this does
           handleError(e) 
       } else {
           currentState.Update(dt)
           currentState.Draw(dt)
       }


       // Calculate dt, sleep to keep constant
       // time, etc.
   }
}

This allows me to have a pause screen loaded like so within the Update method of the calling state:

func (s WorldState) Update (dt float32) {
    if pauseButtonIsPressed() {
        s.owner.Push(NewPauseState())
        return
    }
}

Now the state will look like: [PausedState, WorldState] and the next game loop execution will happen on the PausedState.

Once the user wishes to resume the paused state can Pop itself off the top of the stack and in doing so its Destroy function gets called and the WorldState gets executed on the next loop.


This was probably one of the quickest things I got set up and working in Golang for my game. It has been very useful displaying different scenes, windows and menus.

In case you're wondering, I pass the pointer to a Game struct between states that need access to shared variables by doing s.owner.Push(Inventory(s.Game)). This has the added benefit of keeping the save/load functions within that struct so from the load menu it can create a new instance of Game and using its load function marshal the saved file data before handing it to the WorldState constructor function:

func (s LoadMenu) Update (dt float32) {
    if UserHasSelectedSavedGame() {
        g := &Game{}
        g.Load(filepathname)
        s.owner.Replace(NewWorldState(g))
    }
}

I will be packaging this code into a go library that I will continue to use in future projects and further discuss its utility both here and on my main blog.


🕹️