Sunday, January 9, 2011

Untying the game update loop

In a video game, what do the menu system, an AI agent and an animated HUD component have in common? They are all a pain in the neck to implement over the game update loop.

The difficulty arises from the gap between the concept and the code implementing it. For instance, a menu system is pretty simple to specify:
1. Identify the controller which is used to control the game,
2. Show the main menu
3. Wait for the user to select an entry
4. Depending on the entry, remove the menu and show another screen, which may be another menu, or the game itself.

The game update loop requires that we split up these few steps into tiny little chunks that fit in 16ms time slices. This requires the programmer to introduce variables to keep track of where the program currently is and where it's headed:

type GameState =
| PressStartFadeIn of ...
| PressStartWaitButtonPress of ...
| PressStartFadeOut of ...
| MenuFadeIn of ...
...

let update(gt : GameTime) =
  state <-
    match state with
    | PressStartFadeIn ...

The problem with this approach is that the workflow which was so obvious in the specification is now hidden in a state machine. This approach using state machines is common when implementing simple AI agents and animations.
When dealing with the menu system, another objected-oriented approach is often preferred, with a class per screen type. Transitions from one screen to another are implemented using events and handlers, or hard-coded into the screens themselves. For instance, the press start screen takes care of creating and showing the menu screen when it hides itself. In any case, figuring out the order in which screens come and go by reading the code is not trivial.

Ideally, one would like to program the steps using that sort of code:

while not exit_requested do
  let press_start_screen = new PressStartScreen()
  press_start_screen.Show()
  let player = press_start_screen.GetPlayer()
  press_start_screen.Hide()

  let menu_screen = new MenuScreen()
  menu_screen.Show()
  let entry = menu_screen.GetEntry()
  menu_screen.Remove();
  match entry with
  | Play -> play(); showScores()
  | Scores -> showScores()

Happily, F# makes this possible thanks to so-called computation expressions. I am not going into details in one single post, so for now I will simply refer you to the code I have written. It's all in XNAUtils my library for development of games for the Xbox 360.
I have developed a custom workflow and its associated builder TaskBuilder. There is a class which takes care of executing a set of tasks in type Scheduler. Here are some animations demonstrating how it's used. Another example, from the PressStartScreen:
// This task is chopped in blocks and each block is executed by the scheduler each frame (see Main.Update())
    let press_start_task = task {
        // subtask that makes the "press start" text blink. Executes concurrently
        let blinker =
            sys.Spawn(anim.Task)

        // Task to check if a button is pressed. Sets player when that happens.
        let player : PlayerIndex option ref = ref None
        while (!player).IsNone do
            for p in all_players do
                let state = GamePad.GetState(p)
                if state.IsConnected
                    && (state.Buttons.Start = ButtonState.Pressed
                        || state.Buttons.A = ButtonState.Pressed) then
                    player := Some p
            do! nextFrame()

        // Stop blinking
        blinker.Kill()
    
        // To be nice, wait until the blinker is done.
        // Depending on the blinking period, this could take long as we only check for the kill flag once per blink.
        do! sys.WaitUntil(fun () -> blinker.IsDead)

        // Return the index of the player that pressed start.
        return
            match !player with
            | Some p -> p
            | None -> failwith "Unreachable"
    }

All of this is put together in a sample application. Be sure to take a look at the main code.