Editor

Lists of things: spawning, timers and randomness

Pygame fundamentals · Lesson 6 of 7

Most games have many of something: keep them in a list, add new ones on a timer and drop the ones you no longer need.

A list, a timer and random

Each falling block is a Rect in the list blocks. Every frame moves and draws all of them with one for loop.

spawn_timer counts frames. When it reaches spawn_every, a new block appears at a random place along the top (random.randint) and the timer starts again. Lowering spawn_every as the score grows makes the blocks come faster.

Removing things safely

Blocks that have fallen off the screen must go, or the list grows forever and the game slows down. Removing items from a list while a for loop walks through it skips items, so build a new list of the ones to keep instead:

blocks = [b for b in blocks if b.top < 320]

import random
import pygame

pygame.init()
screen = pygame.display.set_mode((480, 320))
clock = pygame.time.Clock()
font = pygame.font.Font(None, 30)
player = pygame.Rect(220, 280, 36, 24)
blocks = []
spawn_timer = 0
score = 0
alive = True

running = True
while running:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False
        elif event.type == pygame.KEYDOWN and event.key == pygame.K_SPACE and not alive:
            blocks, score, alive = [], 0, True

    if alive:
        keys = pygame.key.get_pressed()
        if keys[pygame.K_LEFT]:
            player.x -= 6
        if keys[pygame.K_RIGHT]:
            player.x += 6
        player.clamp_ip(screen.get_rect())

        spawn_every = max(8, 30 - score // 100)  # faster as you survive
        spawn_timer += 1
        if spawn_timer >= spawn_every:
            spawn_timer = 0
            size = random.randint(16, 40)
            blocks.append(pygame.Rect(random.randint(0, 480 - size), -size, size, size))

        for block in blocks:
            block.y += 4
            if block.colliderect(player):
                alive = False
        blocks = [b for b in blocks if b.top < 320]  # keep the ones still on screen
        score += 1

    screen.fill((16, 18, 22))
    for block in blocks:
        pygame.draw.rect(screen, (235, 95, 85), block, border_radius=4)
    pygame.draw.rect(screen, (255, 212, 59), player, border_radius=6)
    screen.blit(font.render(f"Score {score}   Blocks {len(blocks)}", True, (230, 232, 235)), (12, 10))
    if not alive:
        text = font.render("Hit! Press Space to try again", True, (255, 212, 59))
        screen.blit(text, text.get_rect(center=(240, 160)))
    pygame.display.flip()
    clock.tick(60)

pygame.quit()

Things with more than a position

When each thing needs more than a Rect, such as its own speed or colour, store a dictionary per thing: {"rect": rect, "speed": 5}. When things also need their own behaviour, it is time for classes, which the next lesson covers.

Your turn

Change the programs above in the game panel, then press Run.

  • Give each block its own speed: store dictionaries with a "speed" from 2 to 6.
  • Add coins (yellow circles) that also fall: catching one adds 100 points.
  • Show the best score of the session next to the score.

Space Shooter keeps invaders, bullets, bombs and sparks in lists. Open Space Shooter.