This series can be read out of order, but here are some navigation links for your convenience:
<<Introduction | <Previous post | Next post>
(Note: not to be confused with the game design term)
Game Programming Patterns has a pretty good chapter on these:
If there is one pattern this book couldn’t live without, this is it. Game loops are the quintessential example of a “game programming pattern”. Almost every game has one, no two are exactly alike, and relatively few programs outside of games use them.
If you have some intermediate coding knowledge you might be better served by that article, but I’ll try to summarize for the rest of you.
If you think of a whole program as a big tree of nested function calls (i.e. a bunch of code which executes more code which executes more code), then this is the root. A game (actually, almost any modern application) doesn’t end until the player asks it to stop. This means it needs to keep running in an endless loop until that happens, continuously waiting for input and reacting to it when that happens. Most games also differ from modern applications in that things keep happening even when the player isn’t touching anything. Enemies keep moving, animations keep animating, physics keep being simulated, etc. This means that, once the game is initialized, you naturally end up with a looping set of instructions like this:
Read the player’s input
Update the game (physics, player character, enemies, logic, animations, etc.).
Draw the updated game state to the screen.
If the player didn’t quit, go back to step 1. Otherwise, close the game.
Easy-peasy, right? Well, not quite. If you set up some code to run in an endless loop, it will just run as fast as the CPU will allow it to. This means that, depending on several factors such as the hardware or how intensive the game’s current workload is, the game will run faster or slower, resulting in constant fluctuations in the simulation speed. For example, say I set an enemy to move forward by 0.1 units on every loop. The loop might run 500 times per second on my PC, but only 100 times every second on B-san’s PC. This means the enemy would be moving 50 units per second for me, but only 10 units per second for B-san. Well too slow. Poor B-san. So, how do we fix this? There are multiple options:
Delta Time
Pop open any beginner Unity tutorial and they’ll tell you to make sure you’re multiplying all your speed values by a special value called ‘delta_time’. This is because the Unity’s default ‘Update’ functionality is basically a loop like the one described above; it runs as fast as possible. To account for this, the engine keeps track of the delta_time value, and provides it to you. This is basically the time, in seconds, between when the last loop started and when it ended. By doing things like multiplying speed values by this number, you can have the game simulate things “consistently” even if it’s running at different speeds.
Let’s take a look at our previous example. I set my enemy’s speed; assuming I want it to move 50 units per second I would set the speed value per loop to be 50 multiplied by delta_time. Let’s take a look at how this play’s out on my vs. B-san’s PC:
On my PC, the loop runs on average 500 times per second. This means delta_time usually about 0.002 (1/500). This means my enemy will move 0.1 units per loop (50x0.002), which, lo and behold, adds up to 50 over 500 loops.
On B-san’s PC, the loop runs 100 times per second, so delta_time is 0.01 (1/100). This means the enemy moves 0.5 units per loop (50x0.01) which, once again, adds up to 50 over 100 loops. Hooray!
Anyway, using this sort of thing for any kind of physics code is idiotic. Sorry, unity tutorials.
Say that, as a completely sane person trying to prove a point, I throw my laptop in the oven. The fans can’t keep up, everything heats up, the CPU starts throttling and everything slows down drastically. My loop now runs only 2 times every second. The enemy starts moving 25 units per loop. Sure, in theory they’re moving the same speed as before, but now they’re snapping around, phasing through walls, the simulation starts breaking, the floors start melting, chaos.
The issue here is that, while this method smoothly can handle fluctuations in hardware speed, it won’t produce perfectly consistent results. This makes it fine (or even preferable, for reasons we’ll see later) for stuff like camera movement, visual effects, animations, etc. since you could, in-principle, throw all that stuff out at any given moment and just (mostly) rederive it from the state of the game world without causing anything but a slight visual hiccup. But for anything that will the game relies upon in subsequent loops (such the position of the player, enemies, timers that affect what state they’re in, etc.) even small variations will propagate and cause significant differences in the overall state of the game. So what’s the alternative?
Fixed Update
In our original example, the game ran at varying speeds, but if you were observing the game from loop iteration to loop iteration without caring about how fast each loop was running then everything would appear consistent (i.e. the enemy would move the same distance over 100 loops on both my and B-san’s PC, it would just take longer in real-time on B-san’s side). If we had a way to ensure each loop took the same amount of time no matter what, this would fix the problem.
Despite the dismal state of it’s (mostly unofficial) introductory material, the Unity engine itself is naturally aware of this situation and provides an alternative ‘FixedUpdate’ functionality specifically for physics code. And Game Maker’s normal ‘Step’ functionality just does this by default (and requires that you do some questionable fiddling to even get it to work in the other way). But how does it work?
Step one involves picking a target game speed, i.e. how many times per second you want the loop to run. You may have noticed that I’ve hesitated to call this game speed the “framerate” so far; many engines will do so simply because the visual framerate is a natural number to use as your game speed1. However, many games use a fixed loop for physics tasks that isn’t tied to the visible framerate (for example, Minecraft servers typically run at a fixed 20 ‘ticks per second’, while the visual framerate can vary significantly). But if you want to keep things simple, 60 “fps” is a natural choice for this, since it lets you update the game then draw the updated result once per loop. It also results in a fixed delta time of about 16.67 milliseconds (1/60*1000).
Now that we have our target framerate and delta time, step two involves inserting a variable delay into the loop. Think of each loop as a block of 16.67 milliseconds that we need to fully occupy, we put all of the game tasks at the start of the loop then fill whatever time is left with a delay period. To do this, check the time at the start of the loop, run all the tasks for that loop, then check the time again. Subtract the time at the start with the latest time to get how long it took to do all the tasks, then subtract that result from the target delta time. This tells you how long you need to delay for. Going back to our original example, doing the tasks might take different amounts of time, about 2ms on my PC vs. 10ms on B-san’s PC, but this is all evened out by the added delay (14.67ms for me vs. 6.67ms for B-san). Hooray!
But… is it real hooray… this time… ??? ?
Lag
The big issue with using the fixed loop method for everything is lag. What happens when the time it takes to run tasks is higher than the target delta time? In cases like these, there’s unfortunately no way around slowing down the game. This feels pretty terrible even if the slowdown is minor, it makes the game unplayable on sufficiently weak hardware, and even on computers that can run the game fine most of the time, it can seriously damage the play experience during particularly performance-intensive periods.
It also means players with better hardware can’t take advantage of it to produce a visually smoother experience. This matters less for retro-style 2D games, but for 3D games or games that use 3D elements like models with interpolated animations or smooth cameras it’s a pretty silly limitation.
As we saw in the delta time section, this is not a limitation shared by the other method. Sure, the game might get choppier under worse circumstances, but it will remain decently playable under far worse conditions. This means that, unsurprisingly, best prescription for combatting the problem of lag and variable hardware speeds is an approach that combines both methods.
And that approach is…
Not something I really care to look into.
Huh???
Yeah, for the (retro-style 2D) games I’m making, a simple fixed loop approach is more than good enough. I might tweak things a little to account for slight differences in monitor framerates, but the overall approach will be the same. Also, while it’s possible that I’ll change my mind about this in the future, part of the point of making my own engine is to gain the ability to develop games where lag isn’t really an issue (on any modern device at least), so mitigation strategies become less essential.
Game loops are difficult to talk about comprehensively. Since they’re at the root of your program, they’re necessary from the beginning, but they’re also constantly changing alongside the rest of things. At a certain point, once the engine is in more of a finished state, I’ll probably make a part 2 detailing exactly what I chose to put in mine, but for now this should cover everything I had to consider starting out.
Well, that’s one thing down when it comes to really stress-testing this engine. Next week I’ll talk about the thing I’ve spent the better part of last month working on: the build system!
<<Introduction | <Previous post | Next post>
Many retro games ran at just about 60 loops per second for the same reason that the NTSC standard for analog TV, used in Japan and most of the Americas, runs at around either 30 or 60 fps: the 60hz AC power supply. Resources were limited at the time, so game speeds were tied to the speed of the hardware itself, which was tied to the speed of the power supply. However, PAL, the standard used in most of Europe and the rest of the world, was designed with a 50hz power supply in mind. This is why the “PAL” versions of old games often ran 20% slower than normal, developers could not be bothered to properly adjust for the difference in hardware speed.