8
35

Writing a Game Loop in Haskell

The game loop is the heartbeat of any game or simulation. At every interval, the loop ensures that the world is updated and displayed correctly. This post will introduce you to implementing functional game loops using Haskell.

The World

Before we begin we need to construct a world. The world keeps track of every entity in existence. It can also keep track of things like weather, time of day, and the terrain (among other things).

We will keep this world simple – it will only keep track of objects in two dimensions.

Here we’ve defined the world as a collection of its objects. Each object has a position and a velocity which will move the object in a specific direction.

Before we move further, I will introduce a few helpful methods for manipulating the position and velocity.

These functions add two vectors together, and multiplies a vector by a constant, respectively.

Updating the World

At every interval, the world will be updated. For this two-dimensional world, the objects will move in the direction of their velocity.

Let’s write the update function that takes three parameters

world — The current state of the world.
timeSinceStart — The time elapsed since the world began.
delta — The time elapsed since the world was last updated.

update will return an updated version of the world.

This function maps over all the objects in the current world and updates their position.

(We’ve also defined the type Second to make it easier on anyone reading our code. This is preferential to writing update as

since the term Double ambiguously refers to meters, seconds, or kilograms, etc.)

Repeatedly Updating the World

There will be times in the game loop where we’ll want to make use of faster computers. This means updating multiple times before displaying the world. We will write a function, updateLoop, that will update the world as much as possible within a given amount of time.

The updateLoop function takes four parameters. The first three are identical to the update function’s parameters.

world — The current state of the world.
timeSinceStart — The time elapsed since the world began.
delta — The time elapsed since the world was last updated.

and

timeRemaining — The amount of time we have left to update the world.

Like the update function, we will return the new state of the world. However, it will be accompanied by a few additional pieces of data. We’ll also attach the previous world, the new elapsed time since the start of the game loop, and any time remaining that was too short to run another update. All of this information will be used when we display the world.

Displaying the World

The other half of the game loop is the display function. This is how we get the state of the world to the end user.

You can do this in any number of ways (common approaches rely on text or 2D / 3D graphics).

For this example, we’ll just print the position of each object.

(Note: mapM_ is identical to map but used when running monadic functions (like print). It runs the function print over the list of objects but does not collect the results, like mapM.)

The In-between

Rarely will updateLoop end with 0 seconds of time remaining. More often than not, we will be left with a little bit of extra time that indicates our update and our display functions will be out of sync.

To remedy this, we will write an interpolate function that will blend two worlds together. The interpolate function will take the previous world state, the current world state, and a blend value between 0.0 and 1.0.

currentWorld — The most recent version of the world.
previousWorld — The version of the world one step in the past.
blend — The percentage value, between 0.0 and 1.0, between the previous world and the current world.

with blend being defined as

The blend value functions as follows

The implementation of interpolate will depend heavily on the complexity of the world. Our world is simply a list of objects with their positions in two dimensions. We can make use of our update function to simply move our previous world a small amount into the future.

The Game Loop

Now we have to connect the updateLoop and display functions together. We’ll do this in the function gameLoop.

gameLoop will take

world — The current state of the world.
elapsedTime — The time elapsed since the world began.
delta — The time elapsed since the world was last updated.
currentTime — The current system time.
acc — An accumulator that will hold any excess time returned from the updateLoop function.

Finally, we write our main function which will start the game loop.

(Note: getTimeInSeconds is a helper function to get the current CPU time in seconds.)

Conclusion

That’s all there is – a functional game loop is less than 100 lines of Haskell.

If you’d like to see more articles about games or functional programming (or both), let me know in the comments.

If you’d like to view my in-progress functional game engine, take a look at Daimyo.

 

Show Comments

No Responses Yet

Leave a Reply