The game loop behavioral pattern is essential in games and simulations to make the actions proceed at the proper speed regardless of what the user is doing or how fast the computer is.
A game loop will repeatedly do the following three things:
- Check for input and process it if available. Event-driven operating systems should provide some way to check for events without returning control back to the operating system. This might be called peeking at an event or something like that. With this, you have the ability to check if the user pressed any buttons or moved the mouse, and if nothing happened, then continue doing the work of the game.
- Update game objects. This means you can move things around, create or destroy things, and change any other game object properties.
- Render the display. This means that you clear what used to be drawn on the display and draw the game objects in their new positions or state.
All games should have a loop like this.
This episode explains how to avoid blocking progress while waiting for user input. And it also explains how to manage the game speed. Listen to the full episode or read the full transcript below.
The basic description says that this pattern decouples game time from user input and processor speed.
We’ve actually finished going through all the patterns in the Gang of Four book. There’s a really good book called Game Programming Patterns by Robert Nystrom that I recommend that documents several more patterns applicable to game development.
I remember working for a bank once helping to develop an electronic banking application and I was in charge of creating digital signatures for large customers. A digital signature is a long series of random bits that are generated through cryptography to relate to another series of random bits. This relation is usually based on large prime numbers and needs a lot of calculation. I ran a software application that created these signatures. This software was designed to run on Windows 3.1 which still had some issues running multiple applications. In particular, when an application was running, the operating system could not stop that application to let another application have a turn. Windows relied on cooperative multitasking. This meant that a well mannered application would voluntarily give up control of the computer back to Windows so that Windows could let another application have a turn.
Well, this application I was using was a little too well mannered and it would perform a small portion of calculations and then return control back to Windows. It did this on purpose. Because it needed a true source of random numbers, it could not rely on anything the computer itself could generate. So it used the mouse coordinates. And by doing a little work and returning back to Windows, it would stop until I moved the mouse. I had to continuously move the mouse for two to three minutes while it gathered random mouse positions and did the work needed to calculate a digital signature. If I stopped moving the mouse, then it also stopped calculating.
Imagine a game that played like that. If you stop typing or stop moving the mouse, then the game also stops. You don’t want your game progress to depend on user input.
This pattern helps with one more aspect too and that’s processor speed. Imagine you buy a new game that was just released to take advantage of the latest processors and hardware available. The game plays perfect and becomes one of your favorites. So much so that you keep it.
Now, many years pass and what used to be a fast computer is considered miserably slow. You buy a new computer that’s at least twice as fast and with better graphics too. You wonder how your favorite game would look on your new bigger monitor and dig through your boxes until you find the disk. The excitement is building and you can’t wait to relive those wonderful times. And you realize with horror that the game now plays too fast. Everything’s too fast. You try to move your character and instead of a nice run, your character uncontrollably zooms into walls. There’s no way you can hope to play the game anymore. You wish there was a way to slow your computer down.
You might think I’m making this up but I assure you, we’re past April fools day. This actually did happen. And not just to one badly written game. Almost every game released in the 1980’s and probably quite a few in the 1990’s too had this problem. The solution in those days really was to provide a means to slow down the computer. Early PC makers didn’t want to put a slow mode button though. Would you buy a computer with a button that said “Slow” on it? Instead, they created a button that said “Turbo.” Oh, the psychology of that button. That button wasn’t there to speed up the computer. The computer was meant to run as fast as it could. The button was really there so it could turn the turbo mode off. This would slow down the computer so it ran at the same speed as the early PC computers. And it was there mainly so people could play games designed to run on slower computers.
Maybe you could be forgiven for writing a game like this in the 1980’s but the only place you’ll find computers with a “Slow” button today are in museums.
This design pattern shows you how to write your games so they’re unaffected by user input and processor speed. I’ll explain more right after this message from our sponsor.
( Message from Sponsor )
Most applications these days are event-driven. The computer is capable of doing so many things that even the fastest keyboard typist cannot keep the processor busy. Even the fastest internet download speed can’t keep the processor busy.
But you know what can keep the processor busy? It’s actually called a busy loop for exactly this reason. You can write one in code with just a single line of code. And this one line of code can take your processor up to 100% utilization instantly. After a couple seconds, your fan will turn on. And just like an athlete starts to sweat with a hard workout, your computer will soon start to overheat. Your fan will spin up even faster. Your computer will get very loud and hot very fast. If you’re on battery power, you could probably watch your remaining charge plummet. That is if the computer could actually do anything else other than run your busy loop.
What is this busy loop that’s so deadly to your computer? Simple. A single statement that just says “while true do nothing.” That’s all it takes to send your processor into a mad race to death as it checks true over and over waiting for it to magically turn to false.
You see, for programs that aren’t event-driven, it’s very easy for a developer to write code that checks for some user event such as a key press of a mouse move and finding nothing like that has happened, to just check again. And again. This is just a more elaborate version of a simple “while true” loop.
Modern operating systems are designed to help you write better software by letting your program know when something interesting happens.
When nothing happens that your program needs to know about, then the computer just does other things. Or it might just as likely go to sleep for a while. This is not the type of sleep that most users are accustomed to when a laptop lid is closed. The user won’t even notice the sleep. The fact is that when we’re using our computers to type email or browse the internet, we use a tiny fraction of the capability of the processor. The computer will be sleeping a lot even though it appears ready and responsive to every key we press.
While this might work okay for a word processor, games are different. We want things to keep going in the game even when the user isn’t pressing any buttons.
The typical event-driven loop that responds to an event and goes back to waiting for the next event doesn’t work well for games. Or for simulation programs.
Event-driven operating systems should provide another way to check for events without returning control back to the operating system. This might be called peeking at an event or something like that. With this, you have the ability to check if the user pressed any buttons or moved the mouse and if nothing happened, then continue doing the work of the game. You still need to make sure your game loop doesn’t go into a busy loop. This doesn’t help with that. But it does allow your game to have a loop that repeatedly does three things:
◦ #1 Check for input and process it if available.
◦ #2 Update game objects. This means you can move things around, create or destroy things, and change any other game object properties.
◦ And #3 Render the display. This means that you clear what used to be drawn on the display and draw the game objects in their new positions or state. For a description of how to draw to the screen without mixing old content with new content, listen to episode #82 that describes double-buffers.
Almost all games will have a loop like this. And I can’t think of any right now that wouldn’t have one. You know now how to avoid blocking progress while waiting for user input. But what about the game speed?
The old games thought they knew exactly how fast the computer would run and tuned their game loops to fit as much as possible into the loop. But as computers became faster, the loop finished updating and rendering faster. And what did the loop do? It went right through the loop and updated and rendered again.
One very simple solution then is to check the time at the beginning of the loop and check the time again after processing input, updating, and rendering. Then you can add a delay or a sleep to the end of your loop for whatever time is left over. Maybe you want your game to run at 60 frames per second. That means each iteration of the loop should take 1 second divided by 60 which is about 16 milliseconds. If your loop runs in 10 milliseconds, then just go to sleep for 6 milliseconds before starting the loop again. You check the time each iteration because sometimes the loop might run faster or slower. By adjusting how much time you sleep, you can keep the loop running at a fixed rate.
Be careful with how you check the time and how you sleep though. Some operating systems are not very accurate and while the times might be good enough for an alarm clock, they may not be good enough for such fine control needed by a game loop. You may need to look for something called a real-time clock that your operating system should provide. It might be a little more complicated to use but should give you the accuracy you need.
This works great if your computer is fast enough to have time left over each game loop. But it won’t help with a slow computer that can’t keep up at all. don’t worry, you can change the game loop to handle this too.
One way to do this that’s not recommended is to scale your updates. In other words, if you’re on a slow computer, then change the update so it’ll move game objects faster. This will help the slow computer keep up. But if things get too slow, then your game objects can end up moving by large enough amounts to cause problems.
Let’s say your game has a wall that normal prevents the hero from walking through. You implement such a feature through a process called collision detection. Each update, you check the position of the hero and if you find it occupies the same space as the wall, then you back the hero up a bit. But what if in a single iteration of the loop, the scaling was so high that the hero move far enough so he appeared completely past the wall? This would trick the collision detection into thinking there was no collision. The result will look like the hero gained a new magic ability to walk through walls. Probably not what you wanted.
You need to be careful with scaling for many reasons and this is just one. Instead of scaling the updates, let’s keep the updates working at a fixed scale. For a slow computer that can’t keep up with the game loop, one way to solve the problem is to just not do so much work during the game loop. Think of a sinking ship and the crew throwing cargo overboard in an attempt to lighten the load. That’s exactly what you can do.
You might want the game to draw to the screen at 60 frames per second, but what if we sometimes skipped the drawing? It might be a bit noticeable but it’ll be better than slowly getting behind and going into a busy loop that never has time to rest. By skipping the rendering, we can avoid doing some work and can start another iteration of the game loop. And maybe this time, there’ll be enough time left over to render the changes that were updated and pause a bit to let the processor cool down.
The last thing I’ll mention is that if you use a game engine to help you write a game, then it might provide the game loop for you. You’re back to almost what looks like writing a normal application. But don’t be fooled. There’s a game loop somewhere in there.