Creating games using XNA - Developing complete systems - F# Deep Dives (2015)

F# Deep Dives (2015)

Part 3. Developing complete systems

Chapter 9. Creating games using XNA

Johann Deneux

One of the main difficulties of implementing user interfaces in games is the gap between event-based user interfaces—nowadays the prevalent type—and frame-based simulation frameworks used for games. User interfaces are typically designed to react to user input and remain asleep between user actions. Frame-based simulation divides time into small, discrete quantities and repeatedly updates the state of the simulation.

User interaction often requires a number of steps to be spread out over longer periods, up to several seconds or minutes. Long interactions have to be broken into smaller units of work, which are chained and executed in each frame. Keeping track of the current unit of work and its data commonly leads to condition-heavy code with lots of partially initialized variables. F# offers a number of elegant solutions in the form of discriminated unions and asynchronous computations. Table 1 compares explicit state using mutable variables and enum in C# to implicit state usingasync computations in F#, which are shorter.

Table 1. Comparison of state management in C# and F#

Explicit state with variables in C#

Implicit state with async in F#

enum State {

WaitingForButtonPress,

StartSelectingStorage,

SelectingStorage,

Loading

}

class InitialScreen {

State Current;

PlayerIndex ControllingPlayer;

IAsyncResult AsyncRes;

StorageDevice Storage;

Data data;

public void Update() {

switch (Current) {

case WaitingForButtonPress:

if (/* Button pressed ...*/) {

ControllingPlayer = ...;

Current =

StartSelectingStorage;

}

break;

case StartSelectingStorage:

AsyncRes =

BeginShowSelector(...);

Current =

SelectingStorage;

break;

case SelectingStorage:

if (AsyncRes.IsCompleted) {

Storage =

EndShowSelector(AsyncRes);

Current = Loading;

}

break;

case Loading:

try {

data =

LoadFrom(Storage);

}

catch (SomeException e) {

data = null;

}

}

}

}

let initialScreen =

async {

let! controllingPlayer =

waitForButtonPress

let! storageDevice =

showStorageDeviceSelector

controllingPlayer

let data =

try

loadFrom storageDevice

|> Some

with

| :? SomeException -> None

return data

}

Time to market, efficiency, correctness, and complexity are concepts important to every business. The same is true of games, regardless of budget. Being the first to publish a game in its genre is important because you’ll benefit from additional exposure. But this is only true if your game is stable. If, like me, you’re a hobbyist game developer, your main concern isn’t financial success but managing to complete your project and publish it. My personal experience is that most projects are exciting initially; then they become fun to develop. But implementing all the features you want requires discipline and focus. Final polishing is annoying; passing certification is stressful. Dealing with bugs and angry players after release can drive you to insanity. Working on a title loses its fun, the more time you spend on it. For this reason, any tool that can help you reach completion early and lets you move on to the next project can make the difference between the frustration of an abandoned project and the satisfaction of a published title.

This chapter demonstrates techniques in F# for solving a number of commonly occurring problems in game development. Many of these issues are present in regular desktop and mobile applications, and the solutions presented here apply to these sectors as well. For instance, mobile phone applications offer attractive, smooth interfaces with multiple animation effects while dealing with slow connections to remote data servers. The gap between the continuously running graphical effects and delayed data retrieval leads to the same kind of issues. You’ll also see how data structures other than classes can be used to model data in a simple yet effective manner. Here again, this topic isn’t specific to games—all applications need to model the data they process. Finally, you’ll learn how to glue together functional code and objects provided by Microsoft’s XNA framework.

Using the XNA framework

The XNA framework directly targets three platforms: PCs with Microsoft Windows, Xbox 360, and Windows Phone 7 mobile devices. Other platforms such as iOS mobile devices, PCs with Mac OS X, and Linux are available through Mono and MonoGame.

XNA Game Studio is the development environment that Microsoft makes available for free to develop XNA games. It’s available as a standalone application using Microsoft Visual Studio 2010 C# Express Edition, which allows developers to write games in C# only and as an extension for other editions of Visual Studio 2010. To use F# to develop XNA games, developers must use Visual Studio 2010 Shell with the F# plug-in together with XNA Game Studio or XNA Game Studio with Visual Studio 2010 Professional Edition or higher.

It’s also possible to install XNA Game Studio as an extension for Visual Studio 2013. The extension is compatible with the Express edition for the desktop and is available at http://msxna.codeplex.com.

For further information on toolkits and frameworks for game development in F#, visit http://fsharp.org/apps-and-games.

Now that you understand the problem and the technology we’ll use in this chapter, let’s dig into the structure of a typical game developed using the XNA framework.

Getting started

Before diving into programming techniques, I’ll introduce a simple game used throughout this chapter. I’ll then explore the basics of XNA games, most of which are directly applicable to MonoGame as well.

Defending Europe against incoming missiles

The game is composed of three screens, as you can see in figure 1. The initial screen prompts the player to press a key or a button. After the user complies, the next screen consists of the main game. The game presents the player with a map of Western Europe. Submarines surfacing from the depths of the Atlantic Ocean and the Mediterranean Sea launch missiles aimed at large cities; the player has to intercept missiles by firing a laser from a satellite. This isn’t a game that can be won, because the pace at which missiles are fired increases with time. When the last city is destroyed, the final score is computed based on the amount of time the player managed to resist. The next screen is the scoreboard, which shows the position of the newly achieved score with respect to the 10 earlier best scores.

Figure 1. Screens in the sample game

Understanding the execution flow of an XNA game

Similar to most frameworks, XNA distinguishes between updating the state of the game and rendering it to the screen and the audio system. These two operations are repeatedly performed in a sequence (see figure 2).

Figure 2. Execution of a game, and its flow of asset data

Each operation is implemented by a method of a class that inherits from DrawableGameComponent. The base class provides a Run method that executes the operation depicted in figure 2.

The Run method calls three virtual methods described in table 2. First, the Initialize method is called, which loads the art assets and performs other initialization of the graphical system, such as running the game in full-screen or windowed mode and setting the resolution. This method also sets the game into a state where it’s ready to run. This typically includes setting the position of the player in the game’s world, level data, and enemy positions.

Table 2. Methods in DrawableGameComponent

Method

Description

Initialize : unit -> unit

Called when the component is initialized. Responsible for loading game assets such as textures, music, and sounds.

Update : GameTime -> unit

Called every frame. Updates the state of the game.

Draw : GameTime -> unit

Called every frame. Draws the current state of the game.

Once this is done, the Update and Draw methods are called one after another repeatedly until the game exits. Update reads inputs from devices such as the mouse, keyboard, gamepads, and joysticks. It then updates the state of the game. After it returns, the Draw method renders the state of the world to the screen and to the audio system.

Although this setting is suitable for simulations, it’s impractical for user interfaces. Indeed, most applications that aren’t game oriented are typically implemented using event-driven frameworks. Distributing a computation or a sequence of actions interacting with the user over the cycles imposed by the XNA framework can be done in a number of ways:

· Move the computation to a background thread.

· Break the computation into multiple functions taking callbacks, where each function is passed as the callback to the previous one.

· Turn the computation into a state machine implemented using discriminated unions.

· Implement the computation as an async computation expression.

In the background-thread approach, the computation is executed synchronously. This approach can lead to an unnecessarily large numbers of threads being created. It also doesn’t explicitly support pausing, and communication across threads requires locking in order to avoid race conditions.

The second approach consists of breaking the computation into a number of noninterruptible subcomputations. Each part is responsible for enlisting the remainder of the computation as a so-called callback into a scheduling system that executes the callback at the appropriate time. This approach forces programmers to use continuation-passing style, which affects code readability negatively.

The third approach is viable for simple computations that can be expressed as small state machines. In video games, there are domains where state machines are traditionally popular, such as AI-controlled entities. They also have their use in other applications, typically whenever a specification uses a state machine. The last approach is similar to the callback-based solution, but it’s made significantly easier to use by using dedicated syntax and special support by the compiler.

This chapter demonstrates state machines implemented using discriminated unions, an application of async computation expressions for I/O on potentially slow persistent storage, and the use of pure functions and data structures for the main game.

Structure of an XNA game in Visual Studio

Some of the build and packaging tools in XNA Game Studio don’t support F# applications out of the box. For this reason, the typical solution is composed of a thin top-level C# project used as an entry point (see figure 3). This top-level C# project refers to F# libraries that contain most of the code. Each solution also contains a content project hosting the graphical and audio resources of the game, such as textures, shaders, sprite sheets, fonts, music loops, and sound effects.

Figure 3. An F# XNA game in Visual Studio is organized as a top level in C#, a content project, and the main code in an F# library.

Each targeted platform requires its own set of projects. For instance, if you’re planning to release for the PC and Xbox 360, you’ll need a PC top-level C# project, a PC F# library, and copies of these projects for the Xbox 360. The content projects can be shared between platforms.

Now that we’ve cleared up how to get started, let’s go through each screen of the example game.

Content project

When developing games, the term content or asset is often used where resource is used in other domains. The amount of processing required to transform source assets into final data consumable by the game can be complex compared to serious applications. XNA makes it possible for developers to customize and extend asset processing. The part of the software that processes assets is called the content pipeline. The pipeline and how to customize it are described in the MSDN online library at http://mng.bz/XNc8.

Selecting the input device on the Press Start screen

The task of the first screen, which I’ll call the Press Start screen, is to identify the player who is playing the game and which input device they will be using. On the PC, the only alternative is to use the mouse and keyboard, but on consoles such as the Xbox 360 the situation is a bit more complex. Consoles are typically connected to multiple gamepads, and you don’t want to force the player to use a specific one. Instead, you let them press the Start button on the gamepad of their choice, and your game then uses that gamepad for the rest of the game as the primary input.

Implementing the process of detecting button or key presses, waiting for the button or the key to be released, and notifying the main game is a task that is nontrivial to implement on top of the game-update loop. The code in the following listing is used by two screens in the game: the Press Start screen and in the main game for pausing and resuming.

Listing 1. Input state machines and a constructor for press-then-release sequences

The discriminated union captures the two kinds of states in which a state machine can be. The data associated with the Active discriminant consists of a function that analyzes the state of the input devices and returns the next state. This kind of function is sometimes called acontinuation. The Done discriminant is associated with the final result returned by the machine when it halts.

The waitReleased function simplifies the creation of state machines. It takes three other functions. The pressed function has the signature 'InputState -> bool and is responsible for detecting button or key presses. It has a counterpart called released, which has the same signature and is responsible for detecting that all buttons or keys have been released. The last function, named func, extracts the result from the state of the input device. It has the signature 'InputState -> 'Result.

In waitReleased, there are two functions, which represent each state of the state machine. Initially, the state machine is active in a state where it waits for pressed to return true. To update an active state machine, you must call the continuation with the current state of the input. In the initial state, function waitPressed passes the state of the input to pressed. If it returns true, waitPressed returns the next state, waitReleased . The caller can then move the state machine to the next state. You’ll do that by changing the value of a mutable field in a class.

Note that waitReleased, unlike waitPressed, must be provided a parameter. It expects the final result, computed earlier in waitPressed, in order to return it after released eventually returns true.

Other uses of state machines

The example in this section focuses on state machines to interact with users, but their use extends to other areas as well. In games, state machines are also often used for AI-controlled characters interacting with their virtual environment in the game. Other applications include interaction with a remote server or client in a distributed environment, or any other situations when a process interacts with an external environment.

Figure 4 illustrates the kind of state machine this function creates. The need for distinct pressed and released functions arises from the fact that you want to detect whether a button is pressed on some gamepad and then wait until all buttons on all gamepads are released. Proceeding as soon as the button detected as pressed is released could trigger the next action too early if the user pressed multiple buttons at the same time by mistake.

Figure 4. Skeleton of a state machine to handle keys being pressed and released

Introducing generic data structures and higher-order functions to handle such a seemingly simple task as waiting for a button press may seem excessive, but it helps encapsulate internal state and avoid code duplication. In the game, InputState-Machine is used to handle inputs from gamepads on the one hand and from the keyboard on the other hand. It’s also used to identify the player and the device used to control the game, and to detect when to pause and resume the game. This amounts to four situations, which I consider a point at which avoiding code duplication is worth the effort, regardless of the seemingly simple approach with specific code inlined directly in a class. Avoiding code duplication is a way to save time when fixing bugs, which helps reduce the time for your game and its updates to reach the market.

In this chapter, each screen is implemented as an XNA game component. The game component is registered to the XNA framework, which calls its LoadContent, Update, and Draw methods when appropriate. Game components are implemented as classes inheriting fromDrawableGameComponent.

This game component has a number of mutable fields that are used to keep track of the state machine detecting button and key presses. Mutability is often frowned on by functional programmers, for good reasons. When integrating with object-oriented frameworks such as XNA, I find it appropriate to use the framework (including mutable classes) in the way it was meant to be used. Purity is used at a lower level, where its benefits help avoid bugs stemming from complex interaction between mutable types. Consider the input state machine used to detect when the Start button is pressed and who pressed it. The state machine is implemented using InputStateMachine and waitReleased, which are by themselves free of side effects (see the following listing).

Listing 2. Detecting button presses on multiple gamepads with an input state machine

Type inference

Can you identify the type of getControllingPlayerFromGamePad? Informally speaking, it’s an input state machine, but the concrete type can be hard for a tired programmer to determine and write down. Fortunately, the F# compiler doesn’t require you to write the complete type. Even better, IntelliSense in Visual Studio will tell you if you hover the mouse over the identifier, letting you check that the type is what you expected.

Figure 5 illustrates the concrete input state machine used on the Press Start screen. The game component adopts the object-oriented idioms of the XNA framework, namely mutable class fields and events. The next listing shows how you update the input state machine and then notify listeners using an event.

Figure 5. Concrete state machine used to detect the primary gamepad

Listing 3. Updating the input state machine and sending out a result

The Update method is responsible for updating getControllingPlayerFromGamepad, a mutable reference pointing at the current input state. It checks the current state, and in case , where the machine is still active, it computes the next state . The state of the game component is finally mutated in . In case , the machine has already completed, meaning the user has pressed Start, and raises the event notifying listeners which gamepad is to be used to control the game.

Another mutable field is used to store the graphical resources used to render the screen. An immutable record, Resources, holds the only two resources you’ll use: a font to render “Press Start” and a sprite batch, an XNA object used to render 2D graphics.

Note that one mutable field optionally holding a record whose members are immutable and non-null is preferable to two mutable fields holding potentially null values. The second approach can hypothetically represent the situation where only one of the resources has been acquired, which in reality can’t happen. The following listing shows how game assets are loaded and released.

Listing 4. Loading game assets and releasing their resources

The Draw method checks if resources have been acquired, and if so renders a blinking Press Start at the center of the screen:

override this.Draw(gt) =

match resources with

| Some rsc ->

let isVisible = gt.TotalGameTime.Milliseconds % 1000 < 700

if isVisible then

let text = "Press start"

let pos =

let size = rsc.Font.MeasureString(text)

let safe = this.GraphicsDevice.Viewport.TitleSafeArea

let posx = safe.Left + (safe.Width - int size.X) / 2

let posy = safe.Top + (safe.Height - int size.Y) / 2

Vector2(float32 posx, float32 posy)

try

rsc.Batch.Begin()

rsc.Batch.DrawString(rsc.Font, text, pos, Color.White)

finally

rsc.Batch.End()

| None ->

()

Note that the let bindings size, safe, posx, and posy used to compute the central position of the blinking label are nested in the definition of the position itself. Although it’s not obviously beneficial in such a small method, in longer code blocks, limiting the scope of such variables helps the reader skim over implementation details.

You’ve seen how to use discriminated unions and generic higher-order functions to process input. The code can be customized for different tasks—for instance, to detect which player will control the game, and to detect when to pause the game. It can also be customized to detect inputs from the gamepads or from the keyboard:

The input-detection mechanism presented here can be used for other tasks beyond detecting when a specific button is pressed.

Exercise 1: Detecting sequences and combinations of button presses

A popular football game on Xbox uses combinations of button presses to perform advanced actions. For instance, performing a shot is done by pressing button B, but doing a precise shot requires the user to press button B and the right bumper at the same time.

Timing and order are also relevant. A ground cross requires quickly tapping button X three times. An early ground cross requires quickly tapping button X three times while holding the left bumper. As a last example, pressing button B and then pressing button A and moving the left joystick at the same time performs a fake shot.

The exercise consists of implementing detection of a complex sequence. You should start by designing a state machine that recognizes these sequences and combinations of inputs. There should be two final states indicating whether 1) the sequence was performed correctly and the character in the game should perform the action, or 2) the sequence wasn’t completed. A sequence isn’t completed if the wrong button is pressed or too much time passes between presses.

Once you’ve drawn the state machine, implement it using the InputStateMachine from listing 1.

You’ve also seen how to use pure functional programing to implement an application’s logic with immutable records and higher-order functions free of side effects, and how to use mutable state in classes in an object-oriented framework to keep track of state at the application’s top level.

In a complete game, the Press Start screen is typically followed by the main menu and its submenus. Although they’re more complex than the Press Start screen, their implementation follows the same pattern: to monitor button presses and raise events. The example here omits menus. Let’s now look at updating the main part of the game, consisting of the game logic, and rendering graphical and audio effects.

Modeling and updating the game world

The game is played on a map where cities must be protected from attacks by nuclear missiles launched from submarines. The player controls a pointer with the mouse or the gamepad that can be used to trigger explosions to detonate incoming missiles before they reach cities.

Many ways are available to build data structures and functions that implement the game just described. I like to separate updating the state of the game from its rendering. This approach makes it possible to test the update logic without setting up the user interface.

I keep the description of the level, which is static, separate from the current state of the game, which changes with each frame. Most numeric types use units of measure, which helps avoid bugs in the physics code and in rendering. Typical physics bugs include forgetting to include the frame time, which leads to games that are tied to the rate of refresh. When running the game on faster hardware, the game also plays faster, which increases the level of difficulty. Similarly, rendering code can easily be involuntarily tied to a specific screen resolution, which causes bugs when attempting to run the game on display sizes that weren’t available at the time of writing.

Declaring units of measure is simple:

Numeric types such as int and float32 can be used with units of measure. Vector types, though, aren’t aware of units of measures, and you must write wrappers. The following code shows a wrapper around XNA’s two-dimensional vector:

Note the use of Float32WithMeasure<'M>. This generic function converts a unit-less number to a number with unit of measure 'M. In nongeneric code, adding units of measure can also be done by multiplying by a constant with the right type:

Useful functions working on typed vectors introduced in the previous code are provided in a module:

TypedVector2 is extended to provide convenient operators and constants:

type TypedVector2<[<Measure>] 'M>

with

static member public (*) (k, U) = TypedVector.scale2 (k, U)

static member public (+) (U, V) = TypedVector.add2 (U, V)

static member public Zero = TypedVector2<'M>()

At this point, you can define types to describe the world using units of measure, as shown in the following listing.

Listing 5. Data representing a level in the game

In this modeling, submarines don’t move. Making them movable would make the game a bit more interesting, because the player wouldn’t know the exact positions where submarines might surface.

Exercise 2

Replace Submarine.Position with a data structure representing a closed path defining the travel of the submarine.

A path is a sequence of waypoints. A waypoint is a geographic location and a time. Once a submarine reaches the last waypoint, it travels back to the initial waypoint.

The level of difficulty is controlled by varying the amount of time between missile launches. The longer the cities survive, the shorter the time between missile launches, resulting in a larger number of missiles present on screen (see the next listing).

Listing 6. Data representing the current state of a game

The declaration of ReticlePosition shows the value of units of measure. There are two equally valid options for the unit of measure: meters and pixels. The unit of measure helps you avoid mixing the two options, which would otherwise be indistinguishable from the compiler’s point of view.

In the case of traveling submarines, the state of the world should account for the location of each submarine.

Exercise 3

Replace the field WorldState.Submarines with an array of submarine states. The state of a submarine must account for the amount of time since the submarine last launched a missile and for its current position.

The state of the game is updated by a function that takes a state as input and returns the new state. This function also takes in actions by the players and returns audio events to be played by the rendering system.

Representing the world state in an immutable data structure has a number of advantages. When debugging, it’s easy to compare the state before and after the update, because both states remain available in memory. Another positive point is that it’s possible to execute the update and the render functions in parallel with minimal locking at the state of each frame.

As shown here, the update thread creates a new state at the same time the render thread draws the previous state:

Update thread

Render thread

Initialize state 0 Create state 1 from state 0 Create state 2 from state 1 ...

Render state 0 Render state 1 ...

Figure 6 shows the flow of data through the update and render functions. Listing 7 shows the signature of the update function and the definition of the order and event types.

Figure 6. The update and render functions

Efficiency of immutable data structures in games

I’ve developed an asteroid-shooting game in 3D that requires handling collisions and rendering thousands of asteroids. Both tasks take more than 8 ms each, meaning the game can’t run at 60 frames per second if the update and render functions are run sequentially. Using immutable data structures for the game state made it easy to parallelize the two operations by avoiding data races. The negative aspect is that using immutable data requires producing large amounts of short-lived new copies, which triggers garbage collection frequently. Because rendering and updating each took at most 13 ms, it left a little over 3 ms for the sole purpose of garbage collection, which in this case was sufficient.

Listing 7. The update function

The update function (which takes a world state and a list of orders and returns a new world state and a list of audio events) is pretty long, yet simple. Most of it is implemented using standard functions from the List module (see listing 8). For instance, explosions are updated usingList.map, and old explosions are removed using List.filter. List.fold updates the position of the aiming reticle for the defensive shield and produces the audio event of a defensive explosion.

Listing 8. Updating world state using the List module

The next listing shows the final steps of the update function.

Listing 9. Building the new world state and the list of audio events

Identifier newExplosions is bound to a list of explosions caused by missiles hitting cities and missiles destroyed by shields. Its definition is in the omitted part of listing 9. At the end of the function, you generate a single explosion audio event if at least one new explosion was triggered. The result of the function is a pair composed of the new world state and all audio events generated during this update round.

You’ve seen how to model the game’s data using records and lists, and how to update the state using standard higher-order functions manipulating lists. This approach is an alternative to the class-oriented approach common in the software industry. Instead of relying entirely on the single concept of the class for the purposes of data modeling and modularity, functional programming uses simple data types for data modeling and the concept of modules for modularity. All classes of applications, regardless of domain, can benefit from this simpler approach. You’ve also seen how units of measure can be used to increase code readability and avoid errors in arithmetic expressions. You’ll now see how to render the graphics and audio of the game in each frame.

Rendering the game and the scoreboard

Unlike game-state update logic, rendering doesn’t produce any data. All the work is performed through side effects. Seen from a high level, rendering is a task that takes a state and issues drawing orders. We’ll look at two different kinds of states commonly encountered in games. The active part of the game often looks like a simulation. The action can be decomposed in tiny slices of time, and the player can influence the action continuously. Each state can be computed from the previous state and the user’s input. We’ll also look into rendering the scoreboard using animations. Unlike the previous phase, this isn’t an interactive session. Each state depends on how much time has passed since the animation started.

Rendering the game

In this section, you’ll see how to render submarines and their animations. What makes this case interesting is that it uses a number of animations showing submarines going to the surface of the water, staying at the surface for a while and firing a missile, and then diving back under the water.Figure 7 shows a surfaced submarine in the upper-left corner firing a missile, apparently aimed at London or Paris.

Figure 7. Screen capture from the game

The way I have modeled the game state, information about the state of the animation isn’t directly available. But it can be deduced from the initial firing period of the submarine, Submarine.Period, the current difficulty level, WorldState.Difficulty, and the time since the last launch, WorldState.Submarines.

Adapting data

The kind of problem where the data available isn’t readily usable by a consumer is a common occurrence in software development. The Adapter design pattern is one of the techniques employed in this situation when dealing with objects, and active patterns can be seen as filling a similar role when dealing with immutable data encountered in functional programing.

From the point of view of the rendering code, it would be convenient if the submarine’s state was modeled using a discriminated union, where each alternative represents the type of animation to play. To bridge the two different views, you use an active pattern.

Listing 10. Defining an active pattern to extract animation data from the world state

The active pattern is a special function whose name follows a convention specifying each case in the desired discriminated union. There is a fixed limit of seven cases, and it’s therefore suitable only for simple cases. The active pattern takes a pair of numbers denoting the initial firing period of the submarine and the amount of time that has passed since the last launch. The time between launches is corrected to take into account the current difficulty level . Like any other function, active patterns can be defined as nested closures that capture parts of the environment. In this case, the current state is captured, making it unnecessary to pass the same data in each invocation in the render function.

The active pattern can now be used to provide convenient access to the game’s logic state for rendering animations.

Listing 11. Using the active pattern to animate submarines

A single array of pairs denoting the launch timing data for each submarine is built from two arrays. The first originates from the world’s definition and indicates how much time separates two missile launches at difficulty level 1.0. The second comes from the current state of the game and represents the amount of time spent since the last launch by each submarine. You match each pair with animation states and draw the corresponding animation frame. In the case of the submarine being on the surface , you render the frame from the sprite sheet of the submarine on the surface. For a diving submarine , you render the frame from the sprite sheet for the diving submarine. Note that the time frame, which varies between 0 and 1, is subtracted from 1. The effect is that the diving animation is played backward. A surfacing submarine is drawn using the surfacing animation played forward . A submarine under water isn’t drawn.

You’ve seen how active patterns can provide convenient views of data, and you’ve seen them applied to animating sprites. The next section also explores the topic of animation, specifically vector-based effects in the scoreboard.

Rendering the scoreboard

To make the scoreboard exciting to watch after playing, the final score isn’t shown instantaneously at its correct rank as the board is displayed. Instead, a new entry is added at the bottom, showing the name of the player and a number ticking toward the final score value. The entry rises in the board as the number ticks up, eventually settling at the correct rank, as shown in figure 8.

Figure 8. Scoreboard animation

At each frame, the render function is called to render the current step in the animation sequence. To correctly place onscreen each entry in the scoreboard, the render function must know the current position of the rising entry on the scoreboard. Depending on the value of the new score, the current frame of the animation is one of three cases:

1. The new score was too low to be featured among the 10 best scores. In that case, it’s shown in red below the scoreboard as the last entry.

2. The animation has completed, in which case the new score is rendered in green at its rank in the scoreboard.

3. The animation is going on, and the displayed value is counting toward the score value. The entry is located at the rank of the counter.

The update function is responsible for producing the new current position from the previous position and the amount of time that has passed since the last call.

A record represents a scoreboard and the new score that the player just achieved. The rendering state is modeled as a discriminated union, one discriminant for each case enumerated:

The initialize function produces the initial state, depending on the existing scores and the new score to add:

The match expression matches two expressions. The first is the array of scores; the second is the rank at which the new score should be inserted in the table, knowing that it’s sorted in descending order. The pattern [||] in matches an empty array, and the underscore matches anything. It catches the special case where the table is empty, which occurs when the game is run for the first time. In that case, the animation should jump directly to its final state, where the score is shown at the first position.

The second part of the match expression, consisting of a call to Array.tryFind-Index, tries to find the first score that’s lower than the new score. Knowing that scores are sorted in descending order, if such an entry is found, you’ve found the rank at which the new score should be inserted. Case initiates the animation in the Busy state, which has two parameters consisting of the current rank of the score and the value currently shown, ticking up toward the value of the new score. If no such entry is found, it means the new score is worse than any score currently present in the table, and initiates the animation in a state where the score should be shown with its final value below the table.

The update function computes the new state, given the old one. When the score has reached its rank in the table and its value, the animation stops and remains in the same state. Otherwise, the ticking value of the score is increased by an amount that depends on the time passed since the last update, and the new rank is computed:

The render function renders the current frame of the animation:

Before doing any rendering, you declare a few helper functions to render strings aligned to the left:

Rendering the board is a matter of rendering the part above the current position of the new score (if any), followed by the score, followed by the rest (if any). If the new score was too low to make it to the 10 highest scores, it’s rendered in red; otherwise it’s rendered in green:

The try...finally block ensures that the call to SpriteBatch.Begin is followed by a call to SpriteBatch.End even if exceptions are thrown. In the case where the score is too low to enter the high-scores table, the animation is pretty disappointing, because it results in displaying the score table without any effects.

Exercise 4

When the player fails to produce a score high enough to be featured in the table, do the following: show a consolation message, wait for a button press, let the message fade away, and finally show the table with the low score below it.

If you intend to distribute your XNA game on platforms other than the PC, you should write the top level of the application in C# using the template from XNA Game Studio, and have most of your code in one or multiple F# libraries. F# types and modules are accessible as .NET constructs from other languages, but they aren’t convenient to use. I recommend that you avoid using F#-specific types and functions in interfaces of F# libraries meant to be callable from other languages. The F# code should therefore be wrapped in constructs that are easily accessible from such languages, such as classes and methods. DrawableGameComponent is well suited for this purpose, as shown next.

Listing 12. Wrapping the scoreboard functions and data in a game component

You’ve seen how state machines implemented with discriminated unions can be used to implement animations. Asynchronous methods in XNA present the same kind of challenge as animations: computations that spread over multiple frames are difficult to fit into the update-draw cycle. Unlike animations, interactions with users and storage devices require complex execution flows that are too complex for the technique presented in this section. The next section shows how async computations help deal with these issues.

Performing I/O safely using async

The XNA framework offers an API to perform I/O on a range of platforms, including the Xbox 360, whose access to persistent storage differs a bit from the way the PC platform does it. In particular, it’s worth mentioning that all access to storage requires the interaction of the user when multiple devices are available. Typical Xbox 360 setups include an internal hard disk and external storage in the form of a memory card or a USB stick. Unlike typical PC software, which can assume that reading and writing small amounts of data is immediate, console games must account for the fact that the user may be shown a dialog box asking which device should be used. For this reason, the XNA API for accessing persistent storage in StorageDevice is exclusively asynchronous.

Why not do it with classes in the first place?

To illustrate the qualities of this technique, I’ll show you an alternative approach that uses a record instead of a discriminated union to represent the state of animation. Records are similar to classes, which are the primary construction block in a number of popular object-oriented languages that lack discriminated unions and pattern matching. This approach is representative of the typical implementation of small state machines in these languages:

Using discriminated unions

Using records

type RenderingState =

| DoneAtBottom

| DoneInserted of int

| Busy of int * float32

let update

board

scoreIncrement

dt

state =

match state with

| DoneInserted _

| DoneAtBottom ->

state

| Busy(idx, score) ->

...

At first, it may seem this code is just as compact and readable as the first approach, and we have pattern-matching to thank for that. is an indication that something might be wrong. The record is capable of representing more states than intended, which is in my experience a breeding ground for bugs. In particular, combinations of the Booleans in where all are false, or more than one is true, are combinations that the record can represent but that I don’t want to handle. Another issue is that the relationship between state data and the state is unclear. Is scoremeaningful when isAtBottom is true?

Games typically don’t use this API to load levels, textures, or sounds. They use it for personal preferences and persistent high scores. To access a file, a game must first select a storage device. This is done by requiring one asynchronously using BeginShowSelector andEndShowSelector. This operation returns a StorageDevice or null (for example, if the user canceled the dialog displaying all available storage devices).

After this, the game requests a StorageContainer from the StorageDevice, which is the area where the game can store data. Each game has its own private area, and access to data from other games isn’t possible. Requesting and obtaining a storage device is once again done asynchronously using BeginOpenContainer and EndOpenContainer.

At this point, files can be created, opened, or deleted from the container. I/O is performed on .NET streams as usual.

Tip

In my experience, most newcomers who attempt to publish a game for the first time fail because of storage-related crashes. The API has failure points that must be handled properly for a game to pass certification and make it to the Xbox Live Independent Games Marketplace. For instance, if the player yanks the memory card used for storage between calls to Begin-OpenContainer and EndOpenContainer, an exception is thrown by the latter call. Moreover, each storage device may have at most one storage container opened at any time. It’s therefore important to dispose of instances of StorageContainer as soon as they’re not needed anymore.

The requirements for proper handling of failure aren’t very demanding. That the game should remain responsive is all that’s required. Because the API uses exceptions to report failures, it’s enough to catch these exceptions and handle them in some way, such as reporting them to the user and returning to the main menu or continuing with default data. Nevertheless, properly handling exceptions in state machines can significantly increase the length of the source code and introduce much repetition. The async functionality in F# offers a convenient way to help implement state machines that includes exception handling.

Safe asynchronous programming

Although using exceptions to report errors is often frowned on in guidelines, it’s a common practice. Their use is viable in the case of synchronous programming, but dealing with exceptions in asynchronous scenarios without support from the language is a difficult task. All situations involving asynchronous programing benefit from the async functionality found in F#. Examples involve any kind of application working with a remote server, such as mobile apps, media set top boxes, and distributed systems.

On the Xbox 360, before you can read or write a file, you need to ask for a storage container, which is a two-step operation. First you show the user a storage-device selector, which the user can choose to forcibly close, thus denying you access to a storage device. If the user picks a device, you can proceed to open the area on the device reserved for your application, a so-called storage container:

The guide is part of the operating system of the Xbox 360, and it’s always accessible by pressing the big button at the center of the gamepad. It’s used to perform basic administration of the console and the user account. It’s also used to prompt the user which device should be used. checks that the guide is available before attempting to use it. Attempting to access the guide while it’s already in use results in an exception, which is handled by . In that case, goes back to the beginning through a recursive call in tail position. Note that isn’t enough to safely start using the guide, because the guide may be opened again between and . You could in principle rely solely on , but I’ve observed that repeatedly requesting access to the guide when it’s not available causes the console to become unresponsive.

You’ve seen how async can be used to decouple user interaction from the update-draw cycle. You’ve also seen that it simplifies exception handling. You’ll now learn how to assemble all the parts.

Putting it all together

At this point, you’ve seen how to implement each screen, but you have yet to bind them together into a complete game. Top levels of games typically extend the Game class from the XNA framework:

type Game() as this =

inherit Microsoft.Xna.Framework.Game()

let graphics = new GraphicsDeviceManager(this)

do this.Content.RootDirectory <- "Content"

All the work of chaining and switching between game screens is defined and started from the Initialize override:

The mainTask function takes a PlayerIndex and produces an F# async, which is started at . The method then calls the default implementation of Initialize in the base class. It’s easy to forget this call , in which case graphics and audio assets aren’t properly loaded.

The mainTask function is as follows:

The afterGameplay function provides an async computation that saves the new score table and displays it:

This example demonstrates how multiple asynchronous computations can be combined in a way similar to how functions can call other functions. The simplicity of combining functions is an important building block of large applications. Asynchronous computations add the possibility to control the execution of a computation and spread it over the entire lifetime of a process.

Summary

State machines play a central role in game programming. They’re found in animation techniques and other computations that spread over multiple frames. The examples in this chapter focus on simple graphical user interface components, but the concepts are also applicable to artificial intelligence and animating characters in games.

Discriminated unions and async each contribute to facilitate implementing state machines. Exception handling in async helps you write robust code.

These techniques are also applicable to domains other than games. Most user-facing applications have a user interface that remains reactive, including when waiting for a slow storage device or while communicating over the internet. Many non-game-related mobile apps want to provide similar levels of eye candy, and animations are one way to improve the looks of a user interface. Server-side programs are facing the same challenges for different reasons. They must share execution time with large numbers of other processes on the same machine, while interacting with a slow environment, which requires asynchronous programming.

You’ve seen how to use units of measure to improve code readability and avoid bugs in complex mathematical expressions. Units of measure can be applied to primitive types such as int and float, and to complex types such as vectors. They’re useful in computation engines found in games, simulators, and financial applications. User interfaces and CAD applications also benefit from the ability to distinguish between unscaled numerical data and screen coordinates.

About the author

Johann Deneux has a PhD from the Department of IT at Sweden’s Uppsala University in formal verification of software. He currently works at Prover Technology AB, a company that specializes in the development and verification of safety-critical signaling systems for railways. His background in formal verification and his recent interest in functional programming are put to good use there, where he’s developing software to model, simulate, test, and verify railway signaling systems.

His interests outside of work include developing small games for PC and Xbox 360 using the XNA framework together with F#. He’s the author of Asteroid Sharpshooter, the first F# game published on the Xbox Live Indie Game channel. His experiences with the game led him to build a utility library named XNAUtils for the developing games, available on Bitbucket (https://bitbucket.org/johdex/xnautils). His continued work with yet-unpublished games provided the material for this chapter.