Learning Goals
3 minBy the end of this lesson you can:
- Move a basket Actor with the keyboard and detect collision with a falling Actor.
- Reset a star to the top of the screen after it is caught or missed.
- Play a different sound effect for a catch versus a miss, and track score and lives.
Warm-Up · Collision Recap
5 minYou used colliderect in earlier lessons. Predict what this prints:
import pgzrun basket = Rect((200, 350), (80, 20)) star_y = 360 if basket.collidepoint((240, star_y)): print("Caught!") else: print("Missed!") pgzrun.go()
Show the answer
Output
Caught!
The point (240, 360) is horizontally between 200 and 280, and vertically between 350 and 370 — so it is inside the basket.
New Concept · Respawning Actors
12 minIn a catching game, objects fall from the top, are caught or missed at the bottom, and then appear again. This is called respawning: resetting position and giving the object a new starting point.
Reset pattern
import random def respawn_star(star): star.x = random.randint(20, WIDTH - 20) star.y = -20 # start just above the screen
Placing the star at y = -20 means it begins just off-screen and glides smoothly into view. Randomising x keeps each fall unpredictable.
The catch-or-miss loop
def update(): star.y += STAR_SPEED if star.y > HEIGHT + 20: # star reached the bottom — missed respawn_star(star) elif basket.colliderect(star): # star hit the basket — caught respawn_star(star)
Both branches call respawn_star — but only the catch branch awards points, and only the miss branch costs a life.
Worked Example · Falling Stars Catcher
12 minPriya builds the full game. Save as star_catcher.py. You will need sounds/chime.wav and sounds/thud.wav in your project folder.
# star_catcher.py — part 1: setup and globals import pgzrun import random WIDTH = 480 HEIGHT = 400 TITLE = "Star Catcher" BASKET_SPEED = 5 STAR_SPEED = 3 score = 0 lives = 3 basket = Rect((200, 360), (80, 18)) star = Rect((0, 0), (20, 20)) def respawn_star(): star.x = random.randint(20, WIDTH - 20) star.y = -20 respawn_star()
# star_catcher.py — part 2: update and draw def update(): global score, lives if keyboard.left and basket.left > 0: basket.x -= BASKET_SPEED if keyboard.right and basket.right < WIDTH: basket.x += BASKET_SPEED star.y += STAR_SPEED if star.y > HEIGHT + 20: lives -= 1 sounds.thud.play() respawn_star() elif basket.colliderect(star): score += 10 sounds.chime.play() respawn_star() def draw(): screen.fill("black") screen.draw.filled_rect(basket, "royalblue") screen.draw.filled_circle(star.center, 10, "gold") screen.draw.text(f"Score: {score}", topleft=(8, 8), fontsize=22, color="white") screen.draw.text(f"Lives: {lives}", topleft=(8, 34), fontsize=22, color="white") pgzrun.go()
The global score, lives line is needed because update() writes to those variables. The basket is clamped inside the screen with the basket.left > 0 and basket.right < WIDTH guards.
Try It Yourself
13 minMake the game harder: increase STAR_SPEED by 0.5 every time the score reaches a multiple of 50. Add a line in update() after the catch branch.
Hint
global score, lives # after score += 10 ... if score % 50 == 0 and score > 0: global STAR_SPEED STAR_SPEED += 0.5
Add a second star (star_b) with its own starting position. Both should fall independently. Use a second Rect and duplicate the catch/miss logic for it in update().
Hint
star_b = Rect((0, 0), (20, 20)) def respawn_b(): star_b.x = random.randint(20, WIDTH - 20) star_b.y = -40 # stagger the start so they don't sync respawn_b()
Mini-Challenge · Game Over Screen
8 minWhen lives reaches zero, the game should freeze and display a large "Game Over!" message with the final score. Stars should stop falling. Combine today's game with a simple boolean flag you learned in Level 1.
It works if…
stars stop moving and "Game Over! Score: NN" appears when lives hit 0
Show one possible solution
game_over = False def update(): global score, lives, game_over if game_over: return # freeze everything # ... rest of update logic unchanged def draw(): screen.fill("black") if game_over: screen.draw.text("Game Over!", center=(WIDTH // 2, HEIGHT // 2 - 30), fontsize=48, color="red") screen.draw.text(f"Final Score: {score}", center=(WIDTH // 2, HEIGHT // 2 + 20), fontsize=28, color="white") return # ... rest of draw logic unchanged
The return inside update() short-circuits all movement. The matching return inside draw() skips drawing the basket and star. One flag, two returns.
Recap
3 minA catching game needs three things in update(): move the player, move the falling object, and check two outcomes — caught (sound + score) or missed (sound + life lost). In both cases, call respawn() to reset the object to a new random position above the screen.
Vocabulary Card
- respawn
- Reset a game object to a new starting position after it is caught or exits the screen.
- global
- Keyword that lets a function read and write a variable defined outside it.
- colliderect
- Returns
Trueif twoRectobjects overlap — used to detect a catch. - keyboard.left / keyboard.right
- Boolean flags that are
Truewhile the matching arrow key is held down.
Homework
4 minAdd a high-score tracker to the catcher game. After game over, check whetherscore beats the current high_scorevariable. If it does, update it and display "New High Score!" in gold. If not, show the high score alongside the final score. Save as star_catcher_hs.py and bring a screenshot to the next class.
Sample · star_catcher_hs.py
# star_catcher_hs.py — high score extension # (add this to the game-over branch of your draw() function) high_score = 0 def update(): global score, lives, game_over, high_score if game_over: return # ... movement and collision code from worked example ... if lives <= 0: game_over = True if score > high_score: high_score = score def draw(): screen.fill("black") if game_over: screen.draw.text("Game Over!", center=(WIDTH // 2, 140), fontsize=48, color="red") screen.draw.text(f"Score: {score}", center=(WIDTH // 2, 200), fontsize=28, color="white") if score >= high_score: screen.draw.text("New High Score!", center=(WIDTH // 2, 240), fontsize=26, color="gold") else: screen.draw.text(f"High Score: {high_score}", center=(WIDTH // 2, 240), fontsize=26, color="cyan") return # ... normal draw code ...
High score resets to 0 every run. PZ-32 will show you how to save it to a file so it persists between sessions.