Learn
08.4 | PYGAME: SPRITES, COLLISIONS AND SCORES
01 | WHEN A RECTANGLE BECOMES AN OBJECT
A game with one player and several falling objects quickly needs more than one position variable. A sprite is a game object with a drawable appearance and associated behaviour. In Pygame’s Sprite system, it commonly has an image Surface and a rect defining its position and bounds.
A Group manages sprite membership and supports operations such as updating or drawing several sprites. Inheritance from pygame.sprite.Sprite supplies group-related behaviour; it does not automatically invent movement, scoring or collision rules.
Think of a sprite as a performer and a Group as a cast list. Your update code decides what the performer does, and your collision rule decides what an encounter means.
02 | A SMALL COMPLETE COLLECTION GAME
Run this file with Pygame installed. Three gold circles fall once, and the player moves left and right. A caught circle is worth ten points. It is a learning prototype, not yet the complete arcade project: there are no new waves, lives or restart screen.
super().__init__() initialises Sprite membership support. image.get_rect creates a position rectangle based on the image size. Groups call each sprite’s update with the supplied arguments.
spritecollide compares the player against the star group. With dokill=True, collided stars are removed from their groups so the same overlap cannot score again on the next frame.
import pygame
pygame.init()
try:
screen = pygame.display.set_mode((640, 480))
clock = pygame.time.Clock()
font = pygame.font.Font(None, 28)
class FallingStar(pygame.sprite.Sprite):
def __init__(self, x, y):
super().__init__()
self.image = pygame.Surface((24, 24), pygame.SRCALPHA)
pygame.draw.circle(self.image, (255, 217, 94), (12, 12), 12)
self.rect = self.image.get_rect(topleft=(x, y))
self.y = float(y)
def update(self, dt):
self.y += 140.0 * dt
self.rect.y = round(self.y)
if self.rect.top >= 480:
self.kill()
class Player(pygame.sprite.Sprite):
def __init__(self):
super().__init__()
self.image = pygame.Surface((60, 24))
self.image.fill((36, 152, 143))
self.rect = self.image.get_rect(midbottom=(320, 450))
self.x = float(self.rect.x)
def update(self, dt, direction):
self.x = max(0.0, min(580.0, self.x + direction * 300.0 * dt))
self.rect.x = round(self.x)
player = Player()
stars = pygame.sprite.Group(FallingStar(100, 0), FallingStar(300, 40), FallingStar(500, 80))
players = pygame.sprite.GroupSingle(player)
score = 0
running = True
while running:
dt = min(clock.tick(60) / 1000.0, 0.05)
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
elif event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE:
running = False
if not running:
break
keys = pygame.key.get_pressed()
direction = int(keys[pygame.K_RIGHT]) - int(keys[pygame.K_LEFT])
player.update(dt, direction)
stars.update(dt)
hits = pygame.sprite.spritecollide(player, stars, dokill=True)
score += 10 * len(hits)
screen.fill((18, 28, 38))
stars.draw(screen)
players.draw(screen)
screen.blit(font.render(f"Score: {score}", True, (255, 255, 255)), (16, 16))
pygame.display.flip()
finally:
pygame.quit()03 | IMAGE AND RECT HAVE DIFFERENT JOBS
image holds pixels. rect holds integer position and dimensions. Group.draw uses each sprite’s image and rect; merely changing a separate y variable does not update the drawn location.
The falling star keeps self.y as a float, adds speed * dt, then assigns round(self.y) to self.rect.y. This preserves fractional movement while supplying the integer coordinates expected by the rectangle.
A transparent Surface lets a circular drawing have transparent corners. Its rect still covers the full square image area. Transparent pixels are not automatically excluded from a rectangle collision test.
04 | GROUPS SUPPORT LIFECYCLE
A sprite can belong to more than one Group. For example, a star might belong to both all_drawable and collectibles. Calling kill removes it from all its current groups.
kill does not guarantee immediate destruction of the Python object. If another reference still points to it, that object can remain alive. The method changes membership, which is what drawing and collision loops use.
Remove objects that leave the screen, otherwise an endless spawning game can keep increasing the number of updates. Lifecycle includes creation, active updates and deliberate removal.
05 | COLLISION DETECTION IS NOT THE OUTCOME
Detection answers whether shapes overlap. The outcome may add score, reduce lives, bounce, stop movement or trigger a scene change. Keep those rules separate so a detection function does not secretly decide every game policy.
Pygame’s default sprite collision uses rectangles. Rectangles that only touch along an edge do not overlap under the usual rectangle rule. Testing just before, at and just after the boundary makes the decision visible.
Mask-based checks can compare non-transparent pixels more precisely. They need suitable masks and still require an outcome policy. Use the simplest collision shape that fits the game rather than claiming all rectangle overlaps match the visible art exactly.
06 | SCORE ONCE PER COLLECTED OBJECT
If a star overlaps for several frames and remains active, adding ten each frame gives repeated points for one encounter. Consume or otherwise mark it after the accepted collection.
The prototype removes collided stars through dokill=True, then adds ten times the number of returned hits. Two distinct caught stars can therefore give twenty points in one frame.
Keep score in application state. Render a text Surface from that score when drawing, but do not update score inside the rendering operation. A paused redraw should not award points.
07 | HAZARDS NEED A DAMAGE POLICY
The complete game introduces rocks and three lives. After an accepted rock hit it gives a short invulnerability interval, preventing several overlapping rocks from instantly taking every life.
Invulnerability is state: a remaining duration reduced by dt. The collision still consumes an overlapping rock, but it does not reduce lives again while protection is active. State the rule, because another game might keep the hazard and use a different response.
At high speeds, a moving object can pass from one side of another to the other between updates without an overlapping sampled position. Smaller bounded steps reduce this tunnelling risk; continuous collision techniques address it more directly.
08 | TEST A RECTANGLE OVERLAP
The player occupies x=100 to 148. A 24-pixel star whose left edge is exactly 148 only touches the player’s right edge. Move it one pixel left and compare the result. The illustration assumes matching vertical ranges.
09 | SEPARATE RULES FROM ART
Use simple shapes while validating movement and collisions. Later replace an image, recalculate its rect intentionally and inspect whether the hitbox still matches the intended gameplay. Centre-preserving changes may be preferable to silently moving a sprite when the art changes size.
The final project deliberately uses drawn shapes and Pygame’s default font so the download has no missing art or audio files. Its rules are in a pure Python model; its renderer does not require every object to inherit Sprite.
Sprite and Group are useful organisation tools, not a requirement that every game architecture must use them. This lesson teaches them directly, then the capstone demonstrates a separate model with explicit drawing.
10 | TEST A COLLECTION, NOT JUST A PICTURE
Check one hit, two distinct hits, no overlap, exact edge contact and an off-screen object. After collection, update again and verify the score does not rise for the same star.
For hazard logic, check the first damage event, another collision during protection and one after protection expires. A well-drawn game can still have unfair rules; tests should examine the changing state as well as the visible scene.
OFFICIAL REFERENCE
Use the worked examples first, then consult the official documentation to check widget, input or drawing options. Match it to the version selected in your project environment.
PUT YOUR SKILLS TO WORK
Use the Section 8 challenges to practise these ideas independently. Work in your Python editor, predict results and keep a short test record.
Section 8 challenges Python course navigation