Skip to main content

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):

graph LR A["0,0 Top-Left"] --> B["800,0 Top-Right"] A --> C["0,600 Bottom-Left"] B --> D["800,600 Bottom-Right"] style A fill:#ff6b6b style D fill:#4caf50

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
Six pygame drawing primitives shown in a 3-by-2 grid. Top row left to right: rect with top-left corner labeled x, y and width and height labeled w and h; circle with a center crosshair and a radius arrow labeled; polygon as a triangle with three vertices labeled p1, p2, and p3. Bottom row left to right: line with start and end endpoints labeled and a callout for width; ellipse drawn inside its bounding rectangle with top-left labeled x, y and width and height labeled w and h; arc as a partial outline drawn over a dashed bounding rect with the start and stop angle endpoints labeled. Each shape pairs with its pygame.draw function signature with argument names color-tinted to match the visual features.
Pygame's six drawing primitives at a glance — rect, circle, polygon, line, ellipse, arc — each paired with its 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!

Six pygame drawing primitives — rect, circle, polygon, line, ellipse, arc — each labeled with their key arguments.
Pygame's drawing primitives: rect, circle, polygon, line, ellipse, arc — each shown with its key arguments labeled. The interactive demo lets you tweak parameters and see the result; this diagram pairs each shape with its pygame.draw signature.

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

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!

Six pygame drawing primitives — rect, circle, polygon, line, ellipse, arc — each labeled with their key arguments.
Pygame's drawing primitives: rect, circle, polygon, line, ellipse, arc — each shown with its key arguments labeled. The interactive demo lets you tweak parameters and see the result; this diagram pairs each shape with its pygame.draw signature.

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:

Performance Tips

💡 Optimize Your Graphics

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!

  1. Rainbow Circle: Draw a series of concentric circles with different colors
  2. Moving Shape: Make a shape that follows your mouse cursor
  3. Shape Painter: Create a program where you can draw shapes by clicking
  4. Animated Sprite: Make a sprite that changes color or size over time
  5. Sprite Trail: Create a sprite that leaves a fading trail as it moves

Common Mistakes to Avoid

⚠️ Watch Out For:

Key Takeaways

🏋️‍♂️ 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:

  1. Initialize Pygame and create an 800×600 window with a descriptive caption.
  2. Fill the background (sky) with a light blue colour using screen.fill((135, 206, 235)) at the start of every frame.
  3. Draw the ground as a green filled rectangle covering the bottom third of the screen with pygame.draw.rect.
  4. Draw a yellow sun as a filled circle near the top-right of the screen with pygame.draw.circle.
  5. Draw a brown house body as a filled rectangle on the ground with pygame.draw.rect.
  6. Draw a darker outline around the same house body by re-drawing the rectangle with a non-zero integer width as the last argument.
  7. Draw a triangular roof on top of the house with pygame.draw.polygon by passing a list of three vertex tuples.
  8. Call pygame.display.flip() at the end of the frame, then run a minimal event loop that exits on pygame.QUIT and caps the frame rate with clock.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!