Menu
CoddyTech

Crash

Part of the Game Rules section of Coddy's Pygame journey. Lesson 27 of 29.

Touching an obstacle ends the run. pygame.sprite.spritecollideany(sprite, group) returns the first sprite in the group that touches the sprite, or None.

By default it compares rects. The pictures have see-through corners, the blob most of all, so two rects can overlap while nothing you see touches. A third argument picks a fairer test:

if pygame.sprite.spritecollideany(runner, obstacles, pygame.sprite.collide_mask):
    state = "over"

collide_mask compares the pictures' drawn pixels and ignores the see-through ones. A crash is then a crash the player can see.

The state works as in Meteor Dodge: updating, jumping and new obstacles happen only while it is "play", and the game over screen draws the frozen game with a message.

challenge icon

Challenge

Medium
  1. Add a font, assets/fonts/pixel.ttf at size 32, and state = "play".
  2. Only while the state is "play": jump, add obstacles, update the runner and the obstacles.
  3. When the runner touches an obstacle (collide_mask), set the state to "over".
  4. When the game is over, show Game over in (40, 50, 80) centred at (320, 140).

Try it yourself

import random
import pygame

pygame.init()
screen = pygame.display.set_mode((640, 320))
pygame.display.set_caption("Sky Runner")
clock = pygame.time.Clock()

SKY = (140, 200, 240)
GROUND = (90, 160, 80)
GROUND_Y = 260
GRAVITY = 1
SPAWN = pygame.USEREVENT + 1


class Runner(pygame.sprite.Sprite):
    def __init__(self):
        super().__init__()
        self.frames = [
            pygame.image.load("assets/images/player.png").convert_alpha(),
            pygame.image.load("assets/images/player_walk.png").convert_alpha(),
        ]
        self.image = self.frames[0]
        self.step = 0
        self.rect = self.image.get_rect(midbottom=(100, GROUND_Y))
        self.vy = 0

    def jump(self):
        if self.rect.bottom == GROUND_Y:
            self.vy = -16

    def update(self):
        self.vy += GRAVITY
        self.rect.y += self.vy
        if self.rect.bottom >= GROUND_Y:
            self.rect.bottom = GROUND_Y
            self.vy = 0
        if self.rect.bottom == GROUND_Y:
            self.step += 1
            self.image = self.frames[self.step // 8 % 2]
        else:
            self.image = self.frames[1]


class Obstacle(pygame.sprite.Sprite):
    def __init__(self):
        super().__init__()
        self.image = random.choice(obstacle_images)
        self.rect = self.image.get_rect(midbottom=(664, GROUND_Y))

    def update(self):
        self.rect.x -= speed
        if self.rect.right < 0:
            self.kill()


obstacle_images = [
    pygame.image.load("assets/images/spike_ball.png").convert_alpha(),
    pygame.image.load("assets/images/blob.png").convert_alpha(),
]
runner = Runner()
obstacles = pygame.sprite.Group()
speed = 6
pygame.time.set_timer(SPAWN, 1500)

running = True
while running:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False
        if event.type == pygame.KEYDOWN and event.key in (pygame.K_SPACE, pygame.K_UP):
            runner.jump()
        if event.type == SPAWN:
            obstacles.add(Obstacle())
            pygame.time.set_timer(SPAWN, random.randint(1000, 2000))

    runner.update()
    obstacles.update()

    screen.fill(SKY)
    pygame.draw.rect(screen, GROUND, (0, GROUND_Y, 640, 60))
    screen.blit(runner.image, runner.rect)
    obstacles.draw(screen)
    pygame.display.flip()
    clock.tick(60)

pygame.quit()

All lessons in Game Rules

Practice on your own: Pygame playground