Introduction to Pygame
Game development is one of the most exciting applications of Python. With the Pygame library, you can create a 2D game with graphics, sound, and a user interface in just a few days. This powerful library provides easy access to graphics handling, events, audio, and text, making it ideal for learning, prototyping, and hobby projects.
Pygame is a cross‑platform set of Python modules built on the SDL (Simple DirectMedia Layer) library. This makes it compatible with Windows, macOS, Linux, and even some mobile platforms.
History and Features of Pygame
Pygame was created by Pete Shinners in 2000 as a replacement for PySDL. Since then, the library has been actively developed by a community of contributors and has become the de‑facto standard for creating 2D games in Python.
Main Advantages of Pygame:
- Ease of use — minimal code to create a basic window
- Cross‑platform — works on all major operating systems
- Rich functionality — supports graphics, audio, input, sprites
- Active community — many tutorials, examples, and extensions
- Open source — free LGPL license
Installation and Setup of Pygame
Basic Installation
pip install pygame
Installation with Optional Features
pip install pygame[optional]
Verify Installation
import pygame
print(f"Pygame version: {pygame.version.ver}")
print(f"SDL version: {pygame.version.SDL}")
# List available modules
print("Available modules:")
for module in ['display', 'mixer', 'font', 'image']:
try:
exec(f"pygame.{module}.get_init()")
print(f"✓ {module}")
except:
print(f"✗ {module}")
Pygame Architecture
Core Modules
Pygame consists of several key modules, each responsible for specific functionality:
- pygame.display — window and screen management
- pygame.event — event handling (keyboard, mouse)
- pygame.image — loading and processing images
- pygame.mixer — sound and music handling
- pygame.font — text rendering
- pygame.sprite — sprite and group system
- pygame.time — time and frame‑rate control
- pygame.math — math utilities for games
Basic Setup and Initialization
Simple Initialization
import pygame
import sys
# Initialize all pygame modules
pygame.init()
# Create a window
screen = pygame.display.set_mode((800, 600))
pygame.display.set_caption("My First Pygame Game")
# Create a clock object for timing
clock = pygame.time.Clock()
print("Pygame successfully initialized!")
Extended Initialization with Error Checking
import pygame
import sys
def init_pygame():
"""Initialize pygame with error checking"""
try:
pygame.init()
# Verify individual modules
if not pygame.get_init():
raise Exception("Pygame not initialized")
if not pygame.display.get_init():
raise Exception("Display module not initialized")
print("Pygame initialized successfully")
return True
except Exception as e:
print(f"Initialization error: {e}")
return False
# Usage
if init_pygame():
screen = pygame.display.set_mode((800, 600))
pygame.display.set_caption("Game Initialized")
Main Game Loop
The heart of any Pygame project is the main game loop. It handles event processing, game‑logic updates, and rendering.
Basic Game Loop
import pygame
pygame.init()
screen = pygame.display.set_mode((800, 600))
pygame.display.set_caption("Basic Game Loop")
clock = pygame.time.Clock()
# Colors
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
running = True
while running:
# Event handling
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
running = False
# Game‑logic update
# (add position updates here)
# Rendering
screen.fill(BLACK) # Clear screen
# (draw objects here)
pygame.display.flip() # Update display
# FPS control
clock.tick(60)
pygame.quit()
sys.exit()
Enhanced Game Loop with States
class GameState:
def __init__(self):
self.running = True
self.paused = False
self.fps = 60
def handle_events(self, events):
for event in events:
if event.type == pygame.QUIT:
self.running = False
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_SPACE:
self.paused = not self.paused
def main():
pygame.init()
screen = pygame.display.set_mode((800, 600))
clock = pygame.time.Clock()
game_state = GameState()
while game_state.running:
events = pygame.event.get()
game_state.handle_events(events)
if not game_state.paused:
# Update game only when not paused
pass
# Rendering
screen.fill((0, 0, 0))
if game_state.paused:
font = pygame.font.SysFont("Arial", 48)
text = font.render("PAUSED", True, (255, 255, 255))
screen.blit(text, (350, 275))
pygame.display.flip()
clock.tick(game_state.fps)
pygame.quit()
Working with Graphics and Images
Loading and Displaying Images
import pygame
import os
def load_image(filename, convert_alpha=True):
"""Safely load an image with error handling"""
try:
if convert_alpha:
image = pygame.image.load(filename).convert_alpha()
else:
image = pygame.image.load(filename).convert()
return image
except pygame.error as e:
print(f"Failed to load image {filename}: {e}")
# Create a placeholder
image = pygame.Surface((32, 32))
image.fill((255, 0, 255)) # Pink for missing textures
return image
# Example usage
pygame.init()
screen = pygame.display.set_mode((800, 600))
# Load images
player_image = load_image("player.png")
background = load_image("background.jpg", False)
# Display
screen.blit(background, (0, 0))
screen.blit(player_image, (100, 100))
pygame.display.flip()
Image Transformations
import pygame
import math
def transform_image_demo():
pygame.init()
screen = pygame.display.set_mode((800, 600))
clock = pygame.time.Clock()
# Create a test image
original = pygame.Surface((64, 64))
original.fill((255, 0, 0))
pygame.draw.rect(original, (0, 255, 0), (16, 16, 32, 32))
angle = 0
scale_factor = 1.0
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
# Animate transformations
angle += 2
scale_factor = 1.0 + 0.5 * math.sin(pygame.time.get_ticks() / 1000.0)
# Apply transformations
rotated = pygame.transform.rotate(original, angle)
scaled = pygame.transform.scale(original,
(int(64 * scale_factor), int(64 * scale_factor)))
flipped_h = pygame.transform.flip(original, True, False)
flipped_v = pygame.transform.flip(original, False, True)
# Rendering
screen.fill((0, 0, 0))
screen.blit(original, (100, 100))
screen.blit(rotated, (300, 100))
screen.blit(scaled, (500, 100))
screen.blit(flipped_h, (100, 300))
screen.blit(flipped_v, (300, 300))
pygame.display.flip()
clock.tick(60)
pygame.quit()
Working with Text and Fonts
Text Basics
import pygame
def text_demo():
pygame.init()
screen = pygame.display.set_mode((800, 600))
# Different ways to create fonts
default_font = pygame.font.Font(None, 36) # Default font
system_font = pygame.font.SysFont("Arial", 48) # System font
# Try loading a custom font
try:
custom_font = pygame.font.Font("custom_font.ttf", 24)
except:
custom_font = default_font
# Create text surfaces
texts = [
(default_font.render("Default Font", True, (255, 255, 255)), (50, 50)),
(system_font.render("System Arial", True, (255, 255, 0)), (50, 100)),
(custom_font.render("Custom Font", True, (0, 255, 255)), (50, 200))
]
# Text with outline
outline_text = system_font.render("Outlined Text", True, (255, 255, 255))
outline_shadow = system_font.render("Outlined Text", True, (0, 0, 0))
running = True
clock = pygame.time.Clock()
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
screen.fill((0, 0, 0))
# Draw normal texts
for text_surface, pos in texts:
screen.blit(text_surface, pos)
# Draw outlined text
screen.blit(outline_shadow, (52, 302)) # Shadow
screen.blit(outline_text, (50, 300)) # Main text
pygame.display.flip()
clock.tick(60)
pygame.quit()
Dynamic Text and Animation
import pygame
import math
def animated_text_demo():
pygame.init()
screen = pygame.display.set_mode((800, 600))
font = pygame.font.SysFont("Arial", 48)
clock = pygame.time.Clock()
def create_rainbow_text(text, font, time_offset=0):
"""Create rainbow‑effect text"""
surfaces = []
for i, char in enumerate(text):
hue = (time_offset + i * 20) % 360
color = pygame.Color(0)
color.hsva = (hue, 100, 100, 100)
char_surface = font.render(char, True, color)
surfaces.append(char_surface)
return surfaces
running = True
start_time = pygame.time.get_ticks()
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
current_time = pygame.time.get_ticks()
elapsed = current_time - start_time
screen.fill((0, 0, 0))
# Rainbow text
rainbow_chars = create_rainbow_text("RAINBOW TEXT", font, elapsed / 10)
x_offset = 50
for char_surface in rainbow_chars:
y_offset = 100 + 20 * math.sin((elapsed + x_offset) / 200)
screen.blit(char_surface, (x_offset, y_offset))
x_offset += char_surface.get_width()
# Pulsing text
pulse_scale = 1.0 + 0.3 * math.sin(elapsed / 300)
pulse_font = pygame.font.SysFont("Arial", int(36 * pulse_scale))
pulse_text = pulse_font.render("PULSING TEXT", True, (255, 255, 255))
screen.blit(pulse_text, (200, 300))
pygame.display.flip()
clock.tick(60)
pygame.quit()
Event Handling and User Input
Comprehensive Event Processing
import pygame
class InputHandler:
def __init__(self):
self.keys_pressed = set()
self.keys_just_pressed = set()
self.keys_just_released = set()
self.mouse_pos = (0, 0)
self.mouse_buttons = [False, False, False]
self.mouse_just_clicked = [False, False, False]
def update(self, events):
# Reset "just" states
self.keys_just_pressed.clear()
self.keys_just_released.clear()
self.mouse_just_clicked = [False, False, False]
# Process events
for event in events:
if event.type == pygame.KEYDOWN:
self.keys_pressed.add(event.key)
self.keys_just_pressed.add(event.key)
elif event.type == pygame.KEYUP:
self.keys_pressed.discard(event.key)
self.keys_just_released.add(event.key)
elif event.type == pygame.MOUSEBUTTONDOWN:
self.mouse_buttons[event.button - 1] = True
self.mouse_just_clicked[event.button - 1] = True
elif event.type == pygame.MOUSEBUTTONUP:
self.mouse_buttons[event.button - 1] = False
elif event.type == pygame.MOUSEMOTION:
self.mouse_pos = event.pos
def is_key_pressed(self, key):
return key in self.keys_pressed
def is_key_just_pressed(self, key):
return key in self.keys_just_pressed
def is_mouse_clicked(self, button=1):
return self.mouse_just_clicked[button - 1]
# Example usage
def input_demo():
pygame.init()
screen = pygame.display.set_mode((800, 600))
clock = pygame.time.Clock()
input_handler = InputHandler()
player_pos = [400, 300]
player_speed = 5
running = True
while running:
events = pygame.event.get()
for event in events:
if event.type == pygame.QUIT:
running = False
input_handler.update(events)
# Movement controls
if input_handler.is_key_pressed(pygame.K_LEFT):
player_pos[0] -= player_speed
if input_handler.is_key_pressed(pygame.K_RIGHT):
player_pos[0] += player_speed
if input_handler.is_key_pressed(pygame.K_UP):
player_pos[1] -= player_speed
if input_handler.is_key_pressed(pygame.K_DOWN):
player_pos[1] += player_speed
# Single‑press actions
if input_handler.is_key_just_pressed(pygame.K_SPACE):
print("Space pressed!")
if input_handler.is_mouse_clicked():
print(f"Mouse click at: {input_handler.mouse_pos}")
# Rendering
screen.fill((0, 0, 0))
pygame.draw.rect(screen, (255, 255, 255),
(player_pos[0]-16, player_pos[1]-16, 32, 32))
pygame.display.flip()
clock.tick(60)
pygame.quit()
Working with Sound and Music
Basic Audio System
import pygame
import os
class AudioManager:
def __init__(self):
pygame.mixer.pre_init(frequency=44100, size=-16, channels=2, buffer=512)
pygame.mixer.init()
self.sounds = {}
self.music_volume = 0.7
self.sound_volume = 0.8
def load_sound(self, name, filename):
"""Load a sound effect"""
try:
sound = pygame.mixer.Sound(filename)
sound.set_volume(self.sound_volume)
self.sounds[name] = sound
print(f"Sound '{name}' loaded successfully")
except pygame.error as e:
print(f"Failed to load sound {filename}: {e}")
def play_sound(self, name, loops=0):
"""Play a sound effect"""
if name in self.sounds:
self.sounds[name].play(loops)
def load_music(self, filename):
"""Load background music"""
try:
pygame.mixer.music.load(filename)
pygame.mixer.music.set_volume(self.music_volume)
print(f"Music loaded: {filename}")
except pygame.error as e:
print(f"Failed to load music {filename}: {e}")
def play_music(self, loops=-1):
"""Start background music"""
pygame.mixer.music.play(loops)
def stop_music(self):
"""Stop music"""
pygame.mixer.music.stop()
def pause_music(self):
"""Pause music"""
pygame.mixer.music.pause()
def unpause_music(self):
"""Resume music"""
pygame.mixer.music.unpause()
def set_music_volume(self, volume):
"""Set music volume (0.0‑1.0)"""
self.music_volume = max(0.0, min(1.0, volume))
pygame.mixer.music.set_volume(self.music_volume)
def set_sound_volume(self, volume):
"""Set sound volume (0.0‑1.0)"""
self.sound_volume = max(0.0, min(1.0, volume))
for sound in self.sounds.values():
sound.set_volume(self.sound_volume)
# Example usage
def audio_demo():
pygame.init()
screen = pygame.display.set_mode((800, 600))
clock = pygame.time.Clock()
audio = AudioManager()
# Load sounds (replace with real files)
# audio.load_sound("jump", "jump.wav")
# audio.load_sound("coin", "coin.wav")
# audio.load_music("background.mp3")
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_1:
audio.play_sound("jump")
elif event.key == pygame.K_2:
audio.play_sound("coin")
elif event.key == pygame.K_m:
audio.play_music()
elif event.key == pygame.K_s:
audio.stop_music()
screen.fill((0, 0, 0))
# Display instructions
font = pygame.font.SysFont("Arial", 24)
instructions = [
"1 - Jump sound",
"2 - Coin sound",
"M - Play music",
"S - Stop music"
]
for i, instruction in enumerate(instructions):
text = font.render(instruction, True, (255, 255, 255))
screen.blit(text, (50, 50 + i * 30))
pygame.display.flip()
clock.tick(60)
pygame.quit()
Sprite System and Animation
Basic Sprite Class
import pygame
import math
class AnimatedSprite(pygame.sprite.Sprite):
def __init__(self, x, y, animation_frames, frame_duration=100):
super().__init__()
self.animation_frames = animation_frames
self.frame_duration = frame_duration
self.current_frame = 0
self.last_frame_time = 0
self.image = self.animation_frames[0]
self.rect = self.image.get_rect()
self.rect.center = (x, y)
# Physical properties
self.velocity = pygame.math.Vector2(0, 0)
self.acceleration = pygame.math.Vector2(0, 0)
self.friction = 0.9
def update(self, dt):
# Animation update
current_time = pygame.time.get_ticks()
if current_time - self.last_frame_time > self.frame_duration:
self.current_frame = (self.current_frame + 1) % len(self.animation_frames)
self.image = self.animation_frames[self.current_frame]
self.last_frame_time = current_time
# Physics update
self.velocity += self.acceleration * dt
self.velocity *= self.friction
self.rect.center += self.velocity * dt
# Screen bounds
if self.rect.left < 0 or self.rect.right > 800:
self.velocity.x *= -0.8
if self.rect.top < 0 or self.rect.bottom > 600:
self.velocity.y *= -0.8
self.rect.clamp_ip(pygame.Rect(0, 0, 800, 600))
class Player(AnimatedSprite):
def __init__(self, x, y):
# Create placeholder animation frames
frames = []
for i in range(4):
frame = pygame.Surface((32, 32), pygame.SRCALPHA)
color_intensity = 255 - i * 50
frame.fill((color_intensity, 100, 255))
frames.append(frame)
super().__init__(x, y, frames, 150)
self.speed = 300
def handle_input(self, input_handler, dt):
acceleration = pygame.math.Vector2(0, 0)
if input_handler.is_key_pressed(pygame.K_LEFT):
acceleration.x = -self.speed
if input_handler.is_key_pressed(pygame.K_RIGHT):
acceleration.x = self.speed
if input_handler.is_key_pressed(pygame.K_UP):
acceleration.y = -self.speed
if input_handler.is_key_pressed(pygame.K_DOWN):
acceleration.y = self.speed
self.acceleration = acceleration
def sprite_demo():
pygame.init()
screen = pygame.display.set_mode((800, 600))
clock = pygame.time.Clock()
# Create sprite groups
all_sprites = pygame.sprite.Group()
players = pygame.sprite.Group()
# Create player
player = Player(400, 300)
all_sprites.add(player)
players.add(player)
# Create NPCs
for i in range(5):
npc_frames = []
for j in range(3):
frame = pygame.Surface((24, 24), pygame.SRCALPHA)
frame.fill((255, 200 - j * 50, 100))
npc_frames.append(frame)
npc = AnimatedSprite(100 + i * 120, 100, npc_frames, 200)
npc.velocity = pygame.math.Vector2(50 + i * 20, 30 + i * 10)
all_sprites.add(npc)
input_handler = InputHandler()
running = True
while running:
dt = clock.tick(60) / 1000.0 # Delta time in seconds
events = pygame.event.get()
for event in events:
if event.type == pygame.QUIT:
running = False
input_handler.update(events)
player.handle_input(input_handler, dt)
# Update all sprites
all_sprites.update(dt)
# Rendering
screen.fill((20, 20, 40))
all_sprites.draw(screen)
# Display info
font = pygame.font.SysFont("Arial", 18)
info_text = font.render(f"Sprites: {len(all_sprites)}, FPS: {int(clock.get_fps())}",
True, (255, 255, 255))
screen.blit(info_text, (10, 10))
pygame.display.flip()
pygame.quit()
Collision System
Advanced Collision System
import pygame
import math
class CollisionMixin:
"""Mixin for handling collisions"""
def check_collision_with_group(self, group, kill_on_collision=False):
"""Check collision with a sprite group"""
collided = pygame.sprite.spritecollide(self, group, kill_on_collision)
return collided
def check_circular_collision(self, other, radius1=None, radius2=None):
"""Check circular collision"""
if radius1 is None:
radius1 = min(self.rect.width, self.rect.height) // 2
if radius2 is None:
radius2 = min(other.rect.width, other.rect.height) // 2
distance = math.sqrt((self.rect.centerx - other.rect.centerx)**2 +
(self.rect.centery - other.rect.centery)**2)
return distance < (radius1 + radius2)
def check_pixel_perfect_collision(self, other):
"""Pixel‑perfect collision check"""
return pygame.sprite.collide_mask(self, other)
class CollidableSprite(pygame.sprite.Sprite, CollisionMixin):
def __init__(self, x, y, width, height, color):
super().__init__()
self.image = pygame.Surface((width, height), pygame.SRCALPHA)
self.image.fill(color)
self.rect = self.image.get_rect()
self.rect.center = (x, y)
# Create mask for pixel‑perfect collisions
self.mask = pygame.mask.from_surface(self.image)
# Physical properties
self.velocity = pygame.math.Vector2(0, 0)
self.mass = width * height / 1000 # Approximate mass
def apply_collision_response(self, other, collision_type="elastic"):
"""Apply physical response to a collision"""
if collision_type == "elastic":
# Elastic collision
v1 = self.velocity
v2 = other.velocity
m1 = self.mass
m2 = other.mass
# Simplified elastic collision formula
self.velocity = ((m1 - m2) * v1 + 2 * m2 * v2) / (m1 + m2)
other.velocity = ((m2 - m1) * v2 + 2 * m1 * v1) / (m1 + m2)
elif collision_type == "absorb":
# Absorption
total_momentum = self.velocity * self.mass + other.velocity * other.mass
total_mass = self.mass + other.mass
new_velocity = total_momentum / total_mass
self.velocity = new_velocity
other.velocity = new_velocity
def collision_demo():
pygame.init()
screen = pygame.display.set_mode((800, 600))
clock = pygame.time.Clock()
# Create sprites
sprites = pygame.sprite.Group()
for i in range(10):
sprite = CollidableSprite(
x=100 + i * 60,
y=200 + (i % 3) * 100,
width=30 + i * 2,
height=30 + i * 2,
color=(255 - i * 20, 100 + i * 15, 100)
)
sprite.velocity = pygame.math.Vector2(
(i - 5) * 20,
(i % 2 - 0.5) * 40
)
sprites.add(sprite)
running = True
while running:
dt = clock.tick(60) / 1000.0
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
# Update positions
for sprite in sprites:
sprite.rect.center += sprite.velocity * dt
# Bounce off edges
if sprite.rect.left <= 0 or sprite.rect.right >= 800:
sprite.velocity.x *= -0.9
if sprite.rect.top <= 0 or sprite.rect.bottom >= 600:
sprite.velocity.y *= -0.9
sprite.rect.clamp_ip(pygame.Rect(0, 0, 800, 600))
# Check collisions between sprites
sprite_list = sprites.sprites()
for i, sprite1 in enumerate(sprite_list):
for sprite2 in sprite_list[i+1:]:
if sprite1.rect.colliderect(sprite2.rect):
# Apply physical response
sprite1.apply_collision_response(sprite2, "elastic")
# Separate sprites to avoid sticking
overlap_x = min(sprite1.rect.right - sprite2.rect.left,
sprite2.rect.right - sprite1.rect.left)
overlap_y = min(sprite1.rect.bottom - sprite2.rect.top,
sprite2.rect.bottom - sprite1.rect.top)
if overlap_x < overlap_y:
if sprite1.rect.centerx < sprite2.rect.centerx:
sprite1.rect.right = sprite2.rect.left
else:
sprite1.rect.left = sprite2.rect.right
else:
if sprite1.rect.centery < sprite2.rect.centery:
sprite1.rect.bottom = sprite2.rect.top
else:
sprite1.rect.top = sprite2.rect.bottom
# Rendering
screen.fill((20, 20, 40))
sprites.draw(screen)
pygame.display.flip()
pygame.quit()
Performance Optimization
Methods to Increase FPS
import pygame
import cProfile
import time
class PerformanceMonitor:
def __init__(self):
self.frame_times = []
self.max_samples = 60
def start_frame(self):
self.frame_start = time.time()
def end_frame(self):
frame_time = time.time() - self.frame_start
self.frame_times.append(frame_time)
if len(self.frame_times) > self.max_samples:
self.frame_times.pop(0)
def get_average_fps(self):
if not self.frame_times:
return 0
avg_frame_time = sum(self.frame_times) / len(self.frame_times)
return 1.0 / avg_frame_time if avg_frame_time > 0 else 0
def get_frame_time_ms(self):
if not self.frame_times:
return 0
return self.frame_times[-1] * 1000
class OptimizedSprite(pygame.sprite.Sprite):
def __init__(self, x, y, image_path=None):
super().__init__()
if image_path:
# Proper image optimization
temp_image = pygame.image.load(image_path)
if temp_image.get_alpha() is not None:
self.image = temp_image.convert_alpha()
else:
self.image = temp_image.convert()
else:
# Create optimized surface
self.image = pygame.Surface((32, 32)).convert()
self.image.fill((255, 100, 100))
self.rect = self.image.get_rect()
self.rect.center = (x, y)
# Use Vector2 for faster calculations
self.position = pygame.math.Vector2(x, y)
self.velocity = pygame.math.Vector2(0, 0)
# Flag to skip updates
self.dirty = True
def update(self, dt):
if not self.dirty:
return
self.position += self.velocity * dt
self.rect.center = self.position
# Mark as clean if not moving
if self.velocity.length() < 0.1:
self.dirty = False
def optimization_demo():
pygame.init()
# Optimized pygame settings
screen = pygame.display.set_mode((800, 600), pygame.DOUBLEBUF | pygame.HWSURFACE)
pygame.display.set_caption("Performance Optimization")
clock = pygame.time.Clock()
monitor = PerformanceMonitor()
# Create many optimized sprites
sprites = pygame.sprite.Group()
for i in range(500): # Lots of sprites for testing
sprite = OptimizedSprite(
x=i % 800,
y=(i // 800) * 32
)
sprite.velocity = pygame.math.Vector2(
(i % 200 - 100) / 10,
(i % 150 - 75) / 10
)
sprites.add(sprite)
# Groups for selective updating
moving_sprites = pygame.sprite.Group()
static_sprites = pygame.sprite.Group()
for sprite in sprites:
if sprite.velocity.length() > 0:
moving_sprites.add(sprite)
else:
static_sprites.add(sprite)
# Pre‑render static background
background = pygame.Surface((800, 600)).convert()
background.fill((20, 20, 40))
static_sprites.draw(background)
running = True
frame_count = 0
while running:
monitor.start_frame()
dt = clock.tick(120) / 1000.0 # Higher FPS cap
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_r:
# Restart all sprites
for sprite in sprites:
sprite.dirty = True
sprite.velocity = pygame.math.Vector2(
(frame_count % 200 - 100) / 10,
(frame_count % 150 - 75) / 10
)
# Update only moving sprites
moving_sprites.update(dt)
# Boundary check for moving sprites
for sprite in moving_sprites:
if (sprite.rect.left < 0 or sprite.rect.right > 800 or
sprite.rect.top < 0 or sprite.rect.bottom > 600):
sprite.velocity *= -0.8
sprite.rect.clamp_ip(pygame.Rect(0, 0, 800, 600))
sprite.position = pygame.math.Vector2(sprite.rect.center)
# Optimized rendering
if frame_count % 5 == 0: # Update background less often
screen.blit(background, (0, 0))
# Draw only moving sprites
moving_sprites.draw(screen)
# Show performance stats
if frame_count % 30 == 0: # Update text less often
font = pygame.font.SysFont("Arial", 18)
fps_text = font.render(f"FPS: {monitor.get_average_fps():.1f}", True, (255, 255, 255))
frame_time_text = font.render(f"Frame time: {monitor.get_frame_time_ms():.2f}ms", True, (255, 255, 255))
sprite_count_text = font.render(f"Sprites: {len(sprites)}", True, (255, 255, 255))
# Clear text area
screen.fill((0, 0, 0), (10, 10, 200, 80))
screen.blit(fps_text, (10, 10))
screen.blit(frame_time_text, (10, 30))
screen.blit(sprite_count_text, (10, 50))
pygame.display.flip()
monitor.end_frame()
frame_count += 1
pygame.quit()
Complete Table of Pygame Methods and Functions
| Module | Function/Method | Description | Example Usage |
|---|---|---|---|
| pygame | init() |
Initializes all modules | pygame.init() |
quit() |
Shuts down all modules | pygame.quit() |
|
get_init() |
Checks initialization status | if pygame.get_init(): |
|
| display | set_mode(size, flags) |
Creates a game window | screen = pygame.display.set_mode((800, 600)) |
set_caption(title) |
Sets the window title | pygame.display.set_caption("Game") |
|
flip() |
Updates the full display | pygame.display.flip() |
|
update(rect_list) |
Updates portions of the screen | pygame.display.update([rect1, rect2]) |
|
get_surface() |
Gets the display surface | screen = pygame.display.get_surface() |
|
| event | get() |
Retrieves all events | events = pygame.event.get() |
poll() |
Retrieves a single event | event = pygame.event.poll() |
|
wait() |
Waits for the next event | event = pygame.event.wait() |
|
clear() |
Clears the event queue | pygame.event.clear() |
|
post(event) |
Posts an event to the queue | pygame.event.post(my_event) |
|
| key | get_pressed() |
State of all keys | keys = pygame.key.get_pressed() |
get_mods() |
State of modifier keys | mods = pygame.key.get_mods() |
|
set_repeat(delay, interval) |
Configure key repeat | pygame.key.set_repeat(500, 50) |
|
| mouse | get_pos() |
Cursor position | x, y = pygame.mouse.get_pos() |
get_pressed() |
Mouse button states | buttons = pygame.mouse.get_pressed() |
|
set_pos(pos) |
Set cursor position | pygame.mouse.set_pos((400, 300)) |
|
set_visible(bool) |
Show/hide cursor | pygame.mouse.set_visible(False) |
|
| image | load(filename) |
Load an image | img = pygame.image.load("sprite.png") |
save(surface, filename) |
Save an image | pygame.image.save(screen, "screenshot.png") |
|
| Surface | blit(source, dest) |
Draw a surface | screen.blit(sprite, (100, 100)) |
fill(color) |
Fill with a color | surface.fill((255, 0, 0)) |
|
convert() |
Optimize surface | sprite = sprite.convert() |
|
convert_alpha() |
Optimize with alpha channel | sprite = sprite.convert_alpha() |
|
get_rect() |
Get rectangle | rect = surface.get_rect() |
|
set_alpha(value) |
Set transparency | surface.set_alpha(128) |
|
| transform | scale(surface, size) |
Scale an image | scaled = pygame.transform.scale(img, (64, 64)) |
rotate(surface, angle) |
Rotate an image | rotated = pygame.transform.rotate(img, 45) |
|
flip(surface, xbool, ybool) |
Flip an image | flipped = pygame.transform.flip(img, True, False) |
|
rotozoom(surface, angle, scale) |
Rotate and scale | transformed = pygame.transform.rotozoom(img, 45, 1.5) |
|
| font | SysFont(name, size) |
Create a system font | font = pygame.font.SysFont("Arial", 24) |
Font(filename, size) |
Load a font from file | font = pygame.font.Font("font.ttf", 24) |
|
render(text, antialias, color) |
Render text | text = font.render("Hello", True, (255, 255, 255)) |
|
get_fonts() |
List available fonts | fonts = pygame.font.get_fonts() |
|
| mixer | init() |
Initialize audio | pygame.mixer.init() |
Sound(filename) |
Load a sound | sound = pygame.mixer.Sound("sound.wav") |
|
music.load(filename) |
Load music | pygame.mixer.music.load("music.mp3") |
|
music.play(loops) |
Play music | pygame.mixer.music.play(-1) |
|
music.stop() |
Stop music | pygame.mixer.music.stop() |
|
music.pause() |
Pause music | pygame.mixer.music.pause() |
|
music.set_volume(volume) |
Set music volume | pygame.mixer.music.set_volume(0.7) |
|
| sprite | Sprite() |
Base sprite class | class Player(pygame.sprite.Sprite): |
Group() |
Sprite group | all_sprites = pygame.sprite.Group() |
|
spritecollide() |
Collision detection | hits = pygame.sprite.spritecollide(player, enemies, False) |
|
groupcollide() |
Group collisions | hits = pygame.sprite.groupcollide(bullets, enemies, True, True) |
|
| time | Clock() |
Clock object | clock = pygame.time.Clock() |
tick(fps) |
Limit FPS | clock.tick(60) |
|
get_ticks() |
Time since start (ms) | time = pygame.time.get_ticks() |
|
delay(ms) |
Pause for milliseconds | pygame.time.delay(1000) |
|
wait(ms) |
Non‑blocking pause | pygame.time.wait(100) |
|
| draw | rect(surface, color, rect) |
Draw a rectangle | pygame.draw.rect(screen, (255, 0, 0), (10, 10, 50, 50)) |
circle(surface, color, center, radius) |
Draw a circle | pygame.draw.circle(screen, (0, 255, 0), (100, 100), 25) |
|
line(surface, color, start, end) |
Draw a line | pygame.draw.line(screen, (255, 255, 255), (0, 0), (100, 100)) |
|
polygon(surface, color, points) |
Draw a polygon | pygame.draw.polygon(screen, (0, 0, 255), [(10,10), (50,10), (30,50)]) |
|
| math | Vector2(x, y) |
2D vector | velocity = pygame.math.Vector2(5, 3) |
Vector3(x, y, z) |
3D vector | position = pygame.math.Vector3(10, 20, 30) |
|
| mask | from_surface(surface) |
Create a mask from a surface | mask = pygame.mask.from_surface(sprite) |
overlap(mask, offset) |
Check mask overlap | overlap = mask1.overlap(mask2, (0, 0)) |
|
| gfxdraw | filled_circle() |
Anti‑aliased filled circle | pygame.gfxdraw.filled_circle(screen, x, y, r, color) |
bezier() |
Bezier curve | pygame.gfxdraw.bezier(screen, points, steps, color) |
Creating a Full‑Featured Game: “Asteroids” Example
import pygame
import math
import random
class GameObject:
"""Base class for all game objects"""
def __init__(self, x, y):
self.position = pygame.math.Vector2(x, y)
self.velocity = pygame.math.Vector2(0, 0)
self.angle = 0
self.radius = 20
self.alive = True
def update(self, dt):
self.position += self.velocity * dt
# Screen wrap‑around
if self.position.x < 0:
self.position.x = 800
elif self.position.x > 800:
self.position.x = 0
if self.position.y < 0:
self.position.y = 600
elif self.position.y > 600:
self.position.y = 0
def draw(self, screen):
pass
def check_collision(self, other):
distance = self.position.distance_to(other.position)
return distance < (self.radius + other.radius)
class Ship(GameObject):
def __init__(self, x, y):
super().__init__(x, y)
self.radius = 10
self.thrust = 0
self.rotation_speed = 0
self.bullets = []
self.max_bullets = 5
def update(self, dt):
# Rotation
self.angle += self.rotation_speed * dt
# Thrust
if self.thrust > 0:
thrust_vector = pygame.math.Vector2(0, -self.thrust)
thrust_vector.rotate_ip(self.angle)
self.velocity += thrust_vector * dt
# Friction
self.velocity *= 0.99
super().update(dt)
# Update bullets
for bullet in self.bullets[:]:
bullet.update(dt)
if not bullet.alive:
self.bullets.remove(bullet)
def shoot(self):
if len(self.bullets) < self.max_bullets:
bullet_velocity = pygame.math.Vector2(0, -300)
bullet_velocity.rotate_ip(self.angle)
bullet_velocity += self.velocity
bullet = Bullet(self.position.x, self.position.y, bullet_velocity)
self.bullets.append(bullet)
def draw(self, screen):
# Ship vertices
points = [
pygame.math.Vector2(0, -self.radius),
pygame.math.Vector2(-self.radius//2, self.radius),
pygame.math.Vector2(self.radius//2, self.radius)
]
# Rotate and translate
rotated_points = []
for point in points:
point.rotate_ip(self.angle)
point += self.position
rotated_points.append(point)
pygame.draw.polygon(screen, (255, 255, 255), rotated_points)
# Draw bullets
for bullet in self.bullets:
bullet.draw(screen)
class Bullet(GameObject):
def __init__(self, x, y, velocity):
super().__init__(x, y)
self.velocity = velocity
self.radius = 2
self.lifetime = 2.0 # seconds
self.age = 0
def update(self, dt):
super().update(dt)
self.age += dt
if self.age > self.lifetime:
self.alive = False
def draw(self, screen):
pygame.draw.circle(screen, (255, 255, 0),
(int(self.position.x), int(self.position.y)),
self.radius)
class Asteroid(GameObject):
def __init__(self, x, y, size=3):
super().__init__(x, y)
self.size = size
self.radius = size * 10
self.rotation_speed = random.uniform(-180, 180)
# Random speed
speed = random.uniform(50, 150)
angle = random.uniform(0, 360)
self.velocity = pygame.math.Vector2(speed, 0)
self.velocity.rotate_ip(angle)
# Generate irregular shape
self.vertices = []
num_vertices = 8
for i in range(num_vertices):
angle = (360 / num_vertices) * i
radius_variation = random.uniform(0.8, 1.2)
vertex_radius = self.radius * radius_variation
vertex = pygame.math.Vector2(vertex_radius, 0)
vertex.rotate_ip(angle)
self.vertices.append(vertex)
def update(self, dt):
super().update(dt)
self.angle += self.rotation_speed * dt
def draw(self, screen):
# Rotate and translate vertices
rotated_vertices = []
for vertex in self.vertices:
rotated_vertex = vertex.copy()
rotated_vertex.rotate_ip(self.angle)
rotated_vertex += self.position
rotated_vertices.append(rotated_vertex)
pygame.draw.polygon(screen, (100, 100, 100), rotated_vertices, 2)
def split(self):
"""Split asteroid into smaller pieces"""
if self.size > 1:
fragments = []
for _ in range(2):
fragment = Asteroid(self.position.x, self.position.y, self.size - 1)
# Add random speed to fragments
fragment.velocity += pygame.math.Vector2(
random.uniform(-100, 100),
random.uniform(-100, 100)
)
fragments.append(fragment)
return fragments
return []
class Game:
def __init__(self):
pygame.init()
self.screen = pygame.display.set_mode((800, 600))
pygame.display.set_caption("Asteroids")
self.clock = pygame.time.Clock()
self.font = pygame.font.SysFont("Arial", 24)
self.ship = Ship(400, 300)
self.asteroids = []
self.score = 0
self.lives = 3
self.game_over = False
# Create initial asteroids
for _ in range(5):
while True:
x = random.randint(0, 800)
y = random.randint(0, 600)
# Ensure asteroid does not spawn too close to the ship
if pygame.math.Vector2(x, y).distance_to(self.ship.position) > 100:
self.asteroids.append(Asteroid(x, y))
break
def handle_input(self):
keys = pygame.key.get_pressed()
if keys[pygame.K_LEFT]:
self.ship.rotation_speed = -180
elif keys[pygame.K_RIGHT]:
self.ship.rotation_speed = 180
else:
self.ship.rotation_speed = 0
if keys[pygame.K_UP]:
self.ship.thrust = 200
else:
self.ship.thrust = 0
# Shoot with space (rate‑limited)
if keys[pygame.K_SPACE]:
self.ship.shoot()
def update(self, dt):
if self.game_over:
return
# Update ship
self.ship.update(dt)
# Update asteroids
for asteroid in self.asteroids:
asteroid.update(dt)
# Bullet‑asteroid collisions
for bullet in self.ship.bullets[:]:
for asteroid in self.asteroids[:]:
if bullet.check_collision(asteroid):
# Remove bullet and asteroid
self.ship.bullets.remove(bullet)
self.asteroids.remove(asteroid)
# Increase score
self.score += (4 - asteroid.size) * 100
# Split asteroid
fragments = asteroid.split()
self.asteroids.extend(fragments)
break
# Ship‑asteroid collisions
for asteroid in self.asteroids:
if self.ship.check_collision(asteroid):
self.lives -= 1
if self.lives <= 0:
self.game_over = True
else:
# Relocate ship to safe zone
self.ship.position = pygame.math.Vector2(400, 300)
self.ship.velocity = pygame.math.Vector2(0, 0)
break
# Victory check (all asteroids destroyed)
if not self.asteroids:
# Create next level
for _ in range(min(8, 5 + self.score // 1000)):
while True:
x = random.randint(0, 800)
y = random.randint(0, 600)
if pygame.math.Vector2(x, y).distance_to(self.ship.position) > 100:
self.asteroids.append(Asteroid(x, y))
break
def draw(self):
self.screen.fill((0, 0, 0))
if not self.game_over:
# Draw ship
self.ship.draw(self.screen)
# Draw asteroids
for asteroid in self.asteroids:
asteroid.draw(self.screen)
# UI
score_text = self.font.render(f"Score: {self.score}", True, (255, 255, 255))
lives_text = self.font.render(f"Lives: {self.lives}", True, (255, 255, 255))
self.screen.blit(score_text, (10, 10))
self.screen.blit(lives_text, (10, 40))
if self.game_over:
game_over_text = self.font.render("GAME OVER! Press R to Restart",
True, (255, 255, 255))
text_rect = game_over_text.get_rect(center=(400, 300))
self.screen.blit(game_over_text, text_rect)
pygame.display.flip()
def restart(self):
self.ship = Ship(400, 300)
self.asteroids = []
self.score = 0
self.lives = 3
self.game_over = False
# Create initial asteroids
for _ in range(5):
while True:
x = random.randint(0, 800)
y = random.randint(0, 600)
if pygame.math.Vector2(x, y).distance_to(self.ship.position) > 100:
self.asteroids.append(Asteroid(x, y))
break
def run(self):
running = True
while running:
dt = self.clock.tick(60) / 1000.0
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_r and self.game_over:
self.restart()
self.handle_input()
self.update(dt)
self.draw()
pygame.quit()
# Launch the game
if __name__ == "__main__":
game = Game()
game.run()
Frequently Asked Questions
How can I optimize game performance in Pygame?
To improve performance in Pygame, use the following techniques:
- Apply
convert()andconvert_alpha()to loaded images - Leverage
pygame.sprite.Group()for efficient sprite management - Limit screen refresh rate with
clock.tick() - Avoid creating new objects inside the main loop
- Use
pygame.display.update(rect_list)instead offlip()for partial updates
How do I add sound to a game?
Initialize the mixer module and load audio files:
pygame.mixer.init()
sound_effect = pygame.mixer.Sound("sound.wav")
sound_effect.play()
# Background music
pygame.mixer.music.load("background.mp3")
pygame.mixer.music.play(-1) # -1 for infinite loop
How can I create a menu in a game?
Create separate game states and switch between them as needed.
The Future of AI in Mathematics and Everyday Life: How Intelligent Agents Are Already Changing the Game
Experts warned about the risks of fake charity with AI
In Russia, universal AI-agent for robots and industrial processes was developed