Drawing Shapes and Sprites
Bringing Your Game World to Life!
Now that you understand the game loop, it's time to make things appear on screen! In this lesson, you'll learn how to draw everything from simple shapes to complex sprites. By the end, you'll be creating colorful, animated game graphics! 🎨
Understanding the Pygame Coordinate System
📐 The Canvas Analogy
Imagine your game screen as a piece of graph paper. Each square has an address (x, y):
- X-axis: Goes from left (0) to right (width)
- Y-axis: Goes from top (0) to bottom (height) - this might feel backwards!
- Origin (0, 0): The top-left corner of your screen
Setting Up Your Drawing Surface
# Initialize Pygame and create a display surface
import pygame
import sys
pygame.init()
# Create the display surface (your canvas)
screen = pygame.display.set_mode((800, 600))
pygame.display.set_caption("Drawing Shapes and Sprites")
# Define colors using RGB values (Red, Green, Blue)
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
RED = (255, 0, 0)
GREEN = (0, 255, 0)
BLUE = (0, 0, 255)
YELLOW = (255, 255, 0)
PURPLE = (128, 0, 128)
Drawing Basic Shapes
Drawing Lines
# Draw a line from point A to point B
pygame.draw.line(screen, WHITE, (100, 100), (300, 200), 5)
# Parameters: surface, color, start_pos, end_pos, width
Drawing Rectangles
# Draw a filled rectangle
pygame.draw.rect(screen, RED, (150, 150, 200, 100))
# Parameters: surface, color, (x, y, width, height)
# Draw a rectangle outline
pygame.draw.rect(screen, GREEN, (400, 150, 200, 100), 3)
# The last parameter is the border width
Drawing Circles
# Draw a filled circle
pygame.draw.circle(screen, BLUE, (400, 300), 50)
# Parameters: surface, color, center_pos, radius
# Draw a circle outline
pygame.draw.circle(screen, YELLOW, (200, 300), 50, 3)
Drawing Polygons
# Draw a triangle (or any polygon)
points = [(300, 100), (400, 200), (200, 200)]
pygame.draw.polygon(screen, PURPLE, points)
# Parameters: surface, color, list_of_points
pygame.draw signature. Argument names are color-tinted to match the feature they control: position in teal, size in amber, the line's end point in blue, and arc angles in red. The interactive demo just below lets you tweak parameters live.Interactive Shape Drawing Demo
🎨 Draw Shapes Interactively!
Complete Shape Drawing Example
import pygame
import sys
# Initialize Pygame
pygame.init()
# Set up display
screen = pygame.display.set_mode((800, 600))
pygame.display.set_caption("Shape Gallery")
# Colors
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
RED = (255, 0, 0)
GREEN = (0, 255, 0)
BLUE = (0, 0, 255)
YELLOW = (255, 255, 0)
CYAN = (0, 255, 255)
MAGENTA = (255, 0, 255)
# Clock for FPS
clock = pygame.time.Clock()
# Game loop
running = True
while running:
# Handle events
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
# Fill background
screen.fill(BLACK)
# Draw shapes showcase
# Lines forming a star
center_x, center_y = 400, 100
for i in range(5):
angle = i * 72 * 3.14159 / 180
x = center_x + 50 * pygame.math.Vector2(1, 0).rotate(angle * 180/3.14159).x
y = center_y + 50 * pygame.math.Vector2(1, 0).rotate(angle * 180/3.14159).y
pygame.draw.line(screen, YELLOW, (center_x, center_y), (x, y), 2)
# Rectangles
pygame.draw.rect(screen, RED, (50, 200, 100, 80))
pygame.draw.rect(screen, GREEN, (200, 200, 100, 80), 3)
# Circles
pygame.draw.circle(screen, BLUE, (100, 400), 40)
pygame.draw.circle(screen, CYAN, (200, 400), 40, 5)
# Ellipse
pygame.draw.ellipse(screen, MAGENTA, (350, 350, 150, 80))
# Polygon (hexagon)
points = []
for i in range(6):
angle = i * 60 * 3.14159 / 180
x = 600 + 50 * pygame.math.cos(angle)
y = 400 + 50 * pygame.math.sin(angle)
points.append((x, y))
pygame.draw.polygon(screen, WHITE, points, 2)
# Update display
pygame.display.flip()
clock.tick(60)
# Quit
pygame.quit()
sys.exit()
Working with Sprites
What Are Sprites?
Sprites are 2D images or animations that represent game objects. Think of them as the actors in your game - characters, enemies, items, backgrounds. In Pygame, sprites are more than just images; they're objects that know how to draw themselves and can have behaviors!
Loading and Displaying Images
# Load an image
player_image = pygame.image.load("player.png")
# Optional: Convert for better performance
player_image = player_image.convert_alpha()
# Get the rectangle for positioning
player_rect = player_image.get_rect()
player_rect.center = (400, 300)
# In your game loop:
screen.blit(player_image, player_rect)
# blit = Block Image Transfer (copying pixels to screen)
⚠️ Image Format Tips
- PNG: Best for sprites with transparency
- JPG: Good for backgrounds (no transparency)
- convert(): Use for images without transparency
- convert_alpha(): Use for images with transparency
Creating a Simple Sprite Class
import pygame
class Player(pygame.sprite.Sprite):
def __init__(self, x, y):
super().__init__()
# Create a simple colored rectangle as our sprite
self.image = pygame.Surface((50, 50))
self.image.fill((0, 255, 0)) # Green square
# Get the rectangle for positioning
self.rect = self.image.get_rect()
self.rect.center = (x, y)
# Movement speed
self.speed = 5
def update(self):
# Get keyboard input
keys = pygame.key.get_pressed()
# Move based on input
if keys[pygame.K_LEFT]:
self.rect.x -= self.speed
if keys[pygame.K_RIGHT]:
self.rect.x += self.speed
if keys[pygame.K_UP]:
self.rect.y -= self.speed
if keys[pygame.K_DOWN]:
self.rect.y += self.speed
def draw(self, screen):
screen.blit(self.image, self.rect)
Sprite Groups - Managing Multiple Sprites
# Create sprite groups
all_sprites = pygame.sprite.Group()
enemies = pygame.sprite.Group()
bullets = pygame.sprite.Group()
# Create and add sprites
player = Player(400, 300)
all_sprites.add(player)
for i in range(5):
enemy = Enemy(100 + i * 150, 100)
all_sprites.add(enemy)
enemies.add(enemy)
# In your game loop:
# Update all sprites at once
all_sprites.update()
# Draw all sprites at once
all_sprites.draw(screen)
Transforming Sprites
# Scale an image
scaled_image = pygame.transform.scale(original_image, (100, 100))
# Rotate an image
rotated_image = pygame.transform.rotate(original_image, 45)
# Flip an image
flipped_image = pygame.transform.flip(original_image, True, False)
# Parameters: image, flip_x, flip_y
Interactive Sprite Demo
🎮 Control the Bouncing Sprites!
Click to add sprites! They'll bounce around automatically.
Color Theory for Games
🎨 RGB Color System
Colors in Pygame use RGB (Red, Green, Blue) values from 0 to 255:
- (255, 0, 0) = Pure Red
- (0, 255, 0) = Pure Green
- (0, 0, 255) = Pure Blue
- (255, 255, 255) = White (all colors)
- (0, 0, 0) = Black (no color)
- (128, 128, 128) = Gray (balanced)
Performance Tips
💡 Optimize Your Graphics
- Use convert(): Always convert images after loading for 2-3x speed boost
- Sprite Groups: Use groups to update and draw many sprites efficiently
- Dirty Rectangles: Only redraw parts of the screen that changed
- Pre-scale Images: Scale images once at load time, not every frame
- Limit Transparency: Transparent surfaces are slower to draw
Complete Sprite Example
import pygame
import random
class Star(pygame.sprite.Sprite):
def __init__(self, x, y):
super().__init__()
self.image = pygame.Surface((10, 10))
pygame.draw.circle(self.image, (255, 255, 0), (5, 5), 5)
self.rect = self.image.get_rect()
self.rect.center = (x, y)
self.collected = False
def update(self):
# Make the star pulse
pass
class Player(pygame.sprite.Sprite):
def __init__(self):
super().__init__()
self.image = pygame.Surface((30, 30))
self.image.fill((0, 100, 255))
self.rect = self.image.get_rect()
self.rect.center = (400, 300)
self.speed = 5
def update(self):
keys = pygame.key.get_pressed()
if keys[pygame.K_LEFT]:
self.rect.x -= self.speed
if keys[pygame.K_RIGHT]:
self.rect.x += self.speed
if keys[pygame.K_UP]:
self.rect.y -= self.speed
if keys[pygame.K_DOWN]:
self.rect.y += self.speed
# Keep player on screen
self.rect.clamp_ip(pygame.Rect(0, 0, 800, 600))
# Initialize Pygame
pygame.init()
screen = pygame.display.set_mode((800, 600))
pygame.display.set_caption("Collect the Stars!")
clock = pygame.time.Clock()
# Create sprite groups
all_sprites = pygame.sprite.Group()
stars = pygame.sprite.Group()
# Create player
player = Player()
all_sprites.add(player)
# Create stars
for i in range(10):
star = Star(random.randint(50, 750), random.randint(50, 550))
all_sprites.add(star)
stars.add(star)
# Game loop
running = True
score = 0
while running:
# Handle events
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
# Update
all_sprites.update()
# Check collisions
collected = pygame.sprite.spritecollide(player, stars, True)
score += len(collected)
# Draw
screen.fill((20, 20, 40)) # Dark blue background
all_sprites.draw(screen)
# Display score
font = pygame.font.Font(None, 36)
text = font.render(f"Stars: {score}", True, (255, 255, 255))
screen.blit(text, (10, 10))
# Update display
pygame.display.flip()
clock.tick(60)
pygame.quit()
Practice Exercises
🎯 Try These Challenges!
- Rainbow Circle: Draw a series of concentric circles with different colors
- Moving Shape: Make a shape that follows your mouse cursor
- Shape Painter: Create a program where you can draw shapes by clicking
- Animated Sprite: Make a sprite that changes color or size over time
- Sprite Trail: Create a sprite that leaves a fading trail as it moves
Common Mistakes to Avoid
⚠️ Watch Out For:
- Forgetting flip(): Nothing appears without pygame.display.flip()
- Wrong coordinate system: Remember Y increases downward!
- Not clearing screen: Old frames remain visible creating trails
- Loading images in loop: Load once, use many times
- Forgetting convert(): Huge performance impact
Key Takeaways
- 📐 Pygame uses a coordinate system with (0,0) at top-left
- 🎨 Basic shapes are drawn with pygame.draw functions
- 🖼️ Sprites are objects that combine images with behavior
- 👥 Sprite Groups help manage multiple sprites efficiently
- 🎯 Always convert images and use groups for better performance
🏋️♂️ Practice Exercise: Draw a Simple Scene
🏋️♂️ Exercise 1: A Sun, a Hill, and a House
Objective: Combine several pygame.draw primitives into a single still scene — a sun above a green hill with a small house in front of it — to practise the top-left-origin coordinate system, the surface-color-shape argument pattern that every drawing function follows, and the optional width argument that flips a shape from filled to outlined.
Instructions:
- Initialize Pygame and create an 800×600 window with a descriptive caption.
- Fill the background (sky) with a light blue colour using
screen.fill((135, 206, 235))at the start of every frame. - Draw the ground as a green filled rectangle covering the bottom third of the screen with
pygame.draw.rect. - Draw a yellow sun as a filled circle near the top-right of the screen with
pygame.draw.circle. - Draw a brown house body as a filled rectangle on the ground with
pygame.draw.rect. - Draw a darker outline around the same house body by re-drawing the rectangle with a non-zero integer
widthas the last argument. - Draw a triangular roof on top of the house with
pygame.draw.polygonby passing a list of three vertex tuples. - Call
pygame.display.flip()at the end of the frame, then run a minimal event loop that exits onpygame.QUITand caps the frame rate withclock.tick(60).
💡 Hint
Remember Pygame's Y-axis grows downward from (0, 0) at the top-left, so the sun's centre needs a small y value (e.g. 120) and the ground rect should start at a large y (e.g. 400). For the roof, the polygon points are three (x, y) tuples — the apex sits above the house body and the two base points sit at the body's top-left and top-right corners. To turn the house outline from filled to hollow, add a non-zero integer (e.g. 4) as the last argument of pygame.draw.rect.
✅ Example Solution
import sys
import pygame
pygame.init()
screen = pygame.display.set_mode((800, 600))
pygame.display.set_caption("My First Scene")
clock = pygame.time.Clock()
# Colour tuples (R, G, B)
SKY = (135, 206, 235)
GROUND = ( 76, 175, 80)
SUN = (255, 215, 0)
HOUSE = (139, 90, 43)
OUTLINE = ( 60, 30, 10)
ROOF = (165, 42, 42)
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
# 1. Sky background (clears the back buffer)
screen.fill(SKY)
# 2. Ground: bottom third of the screen
pygame.draw.rect(screen, GROUND, (0, 400, 800, 200))
# 3. Sun: filled circle near the top-right
pygame.draw.circle(screen, SUN, (650, 120), 60)
# 4. House body: filled rect, then outline (width=4)
pygame.draw.rect(screen, HOUSE, (300, 320, 200, 160))
pygame.draw.rect(screen, OUTLINE, (300, 320, 200, 160), 4)
# 5. Roof: triangle polygon — apex above the body's top edge
pygame.draw.polygon(screen, ROOF, [(290, 320), (510, 320), (400, 240)])
pygame.display.flip()
clock.tick(60)
pygame.quit()
sys.exit()
🎯 Quick Quiz
Question 1: In Pygame's coordinate system, where is (0, 0) and which direction does the Y-axis grow?
Question 2: What does the optional integer width argument do at the end of pygame.draw.rect(surface, color, rect, width)?
Question 3: After loading an image with pygame.image.load(...), why is calling .convert() or .convert_alpha() on the result strongly recommended before you blit it every frame?
What's Next?
Now that you can draw shapes and sprites, next we'll learn how to handle keyboard and mouse input to make your games interactive. You'll create games that respond to player actions!