Cookbook

A set of common recipes and design patterns

Game Loop

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import pygame

## start pygame's engines
pygame.init()

## set the screen size
WINDOW_SIZE = (700, 500)

## get a screen
screen = pygame.display.set_mode(WINDOW_SIZE)

## get a clock used for FPS control
clock = pygame.time.Clock()

## a simple flag variable for the loop
done = False


## the main game loop
while not done:

    ## the event loop; used to check for events that occurred since the last time around
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            done = True


    #### update the display and move forward 1 frame
    pygame.display.flip()
    # --- Limit to 60 frames per second
    self.clock.tick(FPS)

Drawing

Using Rect to draw

Rect is a useful PyGame class that is a wrapper around the standard rectangle information.

x = 0
y = 0
width = 100
height = 100
r1 = pygame.Rect(x, y, width, height)

The variable r1 now has access to a variety of different properties

x,y
top, left, bottom, right
topleft, bottomleft, topright, bottomright
midtop, midleft, midbottom, midright
center, centerx, centery
size, width, height
w,h

You can also update r1 using any of those variables. For example:

1
2
3
r1.center = (50,50)
r1.right = 10
r1.bottomright = 75

Bouncing off obstacles

Basic collision detection with screen boundaries

In the simplest case, we are testing to see if our rect is over some threshold. This happens in the case of bouncing off the edges of the screen. For this example, we assume we know the height and width of the window as well.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# W, H are window width and window height

if r1.right > W:
    print("Over right side")
elif r1.left < 0:
    print("over left side")

if r1.top < 0:
    print("Over top")
elif r1.bottom > H:
    print("Over bottom")

Changing direction based on screen boundary collision

Let’s assume that the object in question is moving at some speed. In other words, the x and y properties are being updated by some variable dx and dy. Then, when the object bounces, it should flip the signs of those speeds.

1
2
3
4
5
6
7
8
9
# W, H are window width and window height
r1.x += dx

if r1.right > W or r1.left < 0:
    dx *= -1

r1.y += dy
if r1.top < 0 or r1.bottom > H:
    dy *= -1

Colliding with another Rect

If you wanted to collide with another Rect, there are several different ways you could it. The easiest way is to use the built in functions which test for collision. However, these functions don’t tell you which parts collided. An example of why this is a problem:

  • There is a collision with a Rect and an obstacle from the bottom
  • The Rect’s right side is technically past the obstacle’s left
  • But, the issue is the y-movement, not the x-movement.

The first part of the solution is to update the X and Y parts separately. With this method, one dimension is changed and checked for collisions. Then, the other is changed and checked for collisions.

The second part of the solution is to “snap” the edges of the object and the obstacle together. This just means making them line up exactly so no more collision is taking place.

The below code illustrates the Rect collision code, the separate x and y movements, and the edge snapping.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
'''
in this example, self.rect is the rect of the object you are moving
'''


def move(self, dx, dy, other_rects):

    # move this object in the x direction
    self.rect.x += dx

    # go over each obstacle
    for other_rect in other_rects:


        # if there is a collision
        # since we moved only the x, we know it has to be this object's left or right
        if self.rect.colliderect(other_rect):

            # if dx is positive, it is moving right
            # if the right side is past the other rect's left, snap them together
            if dx > 0 and self.rect.right > other_rect.left:
                self.rect.right = other_rect.left

            # if dx is negative, it is moving left
            # if the left side is past the other rect's right, snap them together
            elif dx < 0 and self.rect.left < other_rect.right:
                self.rect.left = other_rect.right


    # move this object in the y direction
    self.rect.y += dy

    # go over each obstacle
    for other_rect in other_rects:

        # if there is a collision
        # since we moved only the y, we know it has to be this object's top or bottom
        if self.rect.colliderect(other_rect):

            # if dy is positive, it is moving down
            # if the bottom is past the other rect's top, snap them together
            if dy > 0 and self.rect.bottom > other_rect.top:
                self.rect.bottom = other_rect.top

            # if dy is negative, it is moving up
            # if the top is past the other rect's bottom, snap them together
            elif dy < 0 and self.rect.top < other_rect.bottom:
                self.rect.top = other_rect.bottom