Blog Archives

A wild game loop appears!

Figure 1: Game loop, I choose you!

I told you in my previous posts that the physics would update at 20 Hz while we would render frames at 60 Hz.  It is now implemented and available in the v0.0.6.b of Infiniworld.

There were two things to do:

  • make the game loop smarter,
  • make the view able to interpolate.

Frame interpolation.

If we update the frames three times faster than we update the physics, then we end up rendering three times the same scene with all the entities in the same positions, making our game look like it is rendered at 20 FPS instead of 60.  Nobody wants that.  To avoid this, we can modify the AreaView so that it can perform some interpolation between two physics states.

It felt immediately obvious to me that I should interpolate between the last known physics step and the next one.  Of course the next one hasn’t occurred yet, so I should predict it.  I can predict it for example by using a simple Euler integration: this is computationally cheap and noone is likely to notice any error on a time span of a twentieth of a second.  Then I realize it cannot work because it does not predict the collisions at all.  I need to run the full physics engine in order to have my predicted future. But that does not work either: I cannot predict what the user will do.  I can predict what the Entities will do by running their AI, but the player is a mystery.  And then, what ?  At the next time step I have to recompute the same physics step again, this time with the right player input.  This means that I run the physics twice for every step, and half of these computations give a wrong result because the player cannot be easily predicted.  It sounds like a bad idea doesn’t it?

When we try to guess the future we are not really interpolating, we are extrapolating.  And this, for the reasons given in the previous paragraph, will not work well.  What we should do is interpolating between two known physics steps in the past.  This has the strange consequence that the scene we render on the screen does not show the present state, but a state from a very close past.  This made me feel strange at the beginning, until I realized that we are talking about a delay of a sixtieth of a second, which nobody will notice.

I chose to apply a linear interpolation: we can compute it very fast and it looks good enough.  In short, it means that our entities are smoothly moving in straight lines between the positions given by the physics engine.  I could do some splines or fancy things, using more physics steps, but that would not look nicer.

Figure 2: We render frames using positions interpolated from the last two known physics steps.

The EntityView objects need to store three positions now: the position at the last physics state, the position before that, and an interpolated position.  The latter comes from this very simple line of code:

# Snippet from pygame_.EntityView.interpolatePosition
self.int_pos = (self.old_pos * (1 - ratio) + self.new_pos * ratio)

Each RenderFrameEvent now comes with a ratio.  We use this ratio to interpolate the position of all the EntityView sprites.  Then we draw the scene using these interpolated positions.

Note that only the view does this: the model does not know anything about this interpolation.  Not a single line of code is changed in the model and its physics engine.  We respect our Model-View-Controller pattern.

A smarter game loop.

In order to feed the AreaView with a ratio for its interpolation, the game loop has to compute this ratio. But that’s not the only thing the game loop should do. It should:

  • read the inputs from the keyboard/mouse/joypad/touchscreen but also from the network if we play online ;
  • run the physics ;
  • render the frames with the proper interpolation ratio.

But that’s not it, it also should

  • run the physics at a rate that does not depend on the speed of the machine, which means catching up if the rendering takes so much time we are getting late ;
  • have the highest FPS we can achieve, while still limiting it to what makes sense with the refresh rates of out monitors, independently from the physics ;
  • save some battery and keep the CPU cool by sleeping as much as we can.

I am giving you here links to two very famous and well written articles about game loops:

They present various game loops and their behaviors on fast and slow hardwares.  That’s a good read for people who develop on PC and have to worry about guys like me who change their computer once every five years (and buy each time a cheapos one).  The loop I came up with looks pretty much like what they present at the ends of their articles: everything is independent from everything else, the physics is able to catch up when it’s getting late but even in that case it renders frames once in a while (better have horribly slow game than no game at all).  What I added is the possibility for the CPU to take a rest, and some protection against time jumps that the Network Time Protocol can cause on some machines.

There is a weird thing on the tubes…  I was wondering whether my game loop should sleep or not and many people seemed to find perfectly normal to have the game loop eat ALL the CPU (that’s just one source, I read that in several places).  Even Fiedler and Witters do that.  Why ?  I’ve seen screenshots of Minecraft with absurd frame rates (see Figure 3). Do these people know that their monitor is limited at something like 60 Hz ?

Figure 3: Some dude running Minecraft at 556 FPS. Testosterone much?

At the old times of CRT monitors, 100 Hz was not a luxury, it was a need-to-have if you didn’t want to be staring at a stroboscope all day.  Believe me, I can see a refresh rate smaller than 100 Hz on a CRT.  But we have LCD/TFT/Plasma/dunnowhat monitors now where 60 Hz is perfectly healthy.  Why would you render 10 frames to only display one ?  Maybe it makes sense when you use accelerated graphics cards, but that’s still dumb.  It’s going to make your fans turn at full speed which is never pleasant to the ears and prevent you from playing in summer.  And if you are one of these weirdos who like playing with a laptop on battery in the train (or worse, a Pandora!) you are pretty much doomed.

Now, sleeping is not super reliable, we can wake up if a signal is sent to our process, or we may sleep a bit longer than we hoped.  But I designed a game loop robust enough to handle change in speed, so let’s just do it!  I’ll put the CPU to sleep.

Here you can read the code of my game loop: https://github.com/Niriel/Infiniworld/blob/v0.0.6.b/src/loop.py.

Conclusion.

Figure 1 shows the new game loop in action: This picture is totally representative of what Infiniworld will look like in the end{.} Dear font nerd Pokémon fan: I know I didn’t use the right character font, that’s because I pixel-arted the text in the picture myself ; now, go glitch yourself a Mew. Dear rest of the Universe, let this video blow your mind: