The Game Loop

Written by Andy Cardy.

Lunar PandaOne of the most important parts of any game code, must be the game loop. This is the part where all the magic happens. Lunar Panda is no different! You can see the game loop method in LunarPanda.py if you search for the function pandaGameLoop.

As with all game loops, the pandaGameLoop performs a few vital functions.

Time Keeping

In most game loops, we need to calculate the time between loops. By doing this, we can use this length of time in our physics and input (key press) calculations. The benefit of using the time between frames in our calculations is that we can ensure that the animating of Lunar Panda appears the same regardless of whether your device is slow or fast.

In our code, we calculate it as the first thing in the loop:

clock = pygame.time.Clock()

while True:
time_elapsed_secs = clock.tick(50) / 1000.0

The variable time_elapsed_secs is then used in some of the next calls.

Input Handling

The next part of our loop is to handle the input, from you, the player. Let’s face it, if you’re not able to control Mr Panda, it’s going to be a pretty rubbish game. In Lunar Panda, we process events with the following code:

for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
sys.exit()
if event.type==KEYDOWN:
if event.key==K_LEFT:
mrPanda.angleDelta=-1
elif event.key==K_RIGHT:
mrPanda.angleDelta=1
elif event.key==K_SPACE:
mrPanda.thrust(1)
elif event.key==K_ESCAPE:
pygame.mixer.music.stop()
return False
if event.type==KEYUP:
if event.key==K_LEFT or event.key==K_RIGHT:
mrPanda.angleDelta=0
elif event.key==K_SPACE:
mrPanda.thrust(0)

For each loop of the game, we use this code to see which keypress events are queued up and process each of them. So if the user is pressing the left key (or K_LEFT as it’s defined in Python), we change the angle that Mr Panda is rotated at by -1.

If the user changes the angle of Mr Panda, later in the code we see this:

# Spin Mr Panda if needed.
if mrPanda.angleDelta <> 0:
mrPanda.rotate(mrPanda.angleDelta * 360 * time_elapsed_secs * mrPanda.rotationsPerSec)

Which uses the time elapsed between frames to rotate him. If we didn’t have the time_elapsed_secs variable, Mr Panda would rotate quickly on fast machines and slowly on slow machines.

Physics

Each time we loop around our game, we want to update the physics in Mr Panda’s world. For example, each time we loop round, we’ll want to update the effects that gravity is having on Mr Panda. If he’s falling towards the planet at a given speed, the next time we go around the loop, we’ll want him to be falling slightly quicker as gravity would be pulling him in closer to earth.

In calculating the speed, or velocity of Mr Panda, we take into account the time elapsed between frames, his current velocity, his current thrust from his backpack, gravity and the drag or air friction. All these things combine to make quite a good simulation of Panda goodness!

# Apply the effect of thrust and gravity to Mr Panda.
physicsEngine.tick(time_elapsed_secs)

Collision Detection

Of course, where would our game be without being able to collide Mr Panda into stuff. In our collision detection, we detect wether Mr Panda has hit a side of the screen

# Bounce Mr Panda horizontally off the screen.
if mrPanda.x < mrPanda.width() / 2 and mrPanda.velocityX < 0:
mrPanda.x = mrPanda.width() / 2
mrPanda.velocityX *= -1
elif mrPanda.x > screen_width_pixels - mrPanda.width() / 2 and mrPanda.velocityX > 0:
mrPanda.x = screen_width_pixels - mrPanda.width() / 2
mrPanda.velocityX *= -1

or indeed the landscape.

# Check to see if Mr Panda is touching the ground.
if pygame.sprite.collide_mask(mrPanda, lunarSurface):
mrPanda.thrust(0)
pygame.mixer.music.stop()

if math.fabs(mrPanda.getAngle()) <= maxLandingAngle and mrPanda.velocityY >= maxLandingVelocity:
# Mr Panda has landed safely!
scoreInfo = lunarSurface.isPandaOnFlat(mrPanda)
if scoreInfo[0]:
pandaLanded(mrPanda, scoreInfo[1], lunarSurface, gameStats)
return True # 'True' will make the next level start.

# Mr Panda crashed!
pandaDeath()
return False # 'False' will make the game go back to the title screen.

In the code to detect landing, we also check to see how fast and at which angle and whether he’s landing on a flat. If all of these are within parameters, then he’s landed successfully. Job done!

Rendering

And finally, we get to one of the most imporant parts, rendering! This is where we take all our calculations and render Mr Panda and the rest of the word onto the screen so we can see it.

# Redraw the screen.
lunarSurface.rect.topleft = (0, background_image_height - lunarSurface.rect.height + screenScroll)
redrawScreen(mrPanda, lunarSurface)

# .. other stuff

show_stats(mrPanda)
show_score(gameStats)
pygame.display.update()

And the redrawScreen call is defined as:

def redrawScreen(mrPanda, lunarSurface):

"""
Draw the 'space' background, the ground, and Mr Panda.
This is called several times a second to keep the screen updated.

"""

global screenScroll


screen.blit(background, (0, 0), (0, -screenScroll, screen_width_pixels, screen_height_pixels))
screen.blit(lunarSurface.image, lunarSurface.rect)
screen.blit(mrPanda.image, mrPanda.rect)

So you can see that each loop (or frame) we blit (or copy images in memory) to the appropriate place on the screen. The appropriate places have been calculated from all the earlier bits in the game loop.

After all the rendering is done we call pygame.display.update(). This puts what we’ve blitted to the screen on the users display and clears the screen for us to update again next time the loop comes around.

And that’s pretty much it. Feel free to play around and see what you can do!