Pygame - Game Development

онлайн тренажер по питону
Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

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() and convert_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 of flip() 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.

News