Pygame – разработка игр

онлайн тренажер по питону
Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Разработка игр на Python с библиотекой Pygame: Полное руководство

Введение в Pygame

Разработка игр — одно из самых увлекательных применений Python. Благодаря библиотеке Pygame создать 2D-игру с графикой, звуком и пользовательским интерфейсом можно за считанные дни. Эта мощная библиотека предоставляет простой доступ к управлению графикой, событиям, звукам и текстом, и является идеальной для обучения, прототипирования и хобби-проектов.

Pygame представляет собой кроссплатформенный набор модулей Python, построенный на основе библиотеки SDL (Simple DirectMedia Layer). Это делает её совместимой с Windows, macOS, Linux и даже некоторыми мобильными платформами.

История и особенности Pygame

Pygame была создана Питом Шиннерсом в 2000 году как замена PySDL. С тех пор библиотека активно развивается сообществом разработчиков и стала стандартом де-факто для создания 2D-игр на Python.

Основные преимущества Pygame:

  • Простота использования — минимальный код для создания базового окна
  • Кроссплатформенность — работает на всех основных операционных системах
  • Богатый функционал — поддержка графики, звука, ввода, спрайтов
  • Активное сообщество — множество туториалов, примеров и библиотек
  • Открытый исходный код — свободная лицензия LGPL

Установка и настройка Pygame

Базовая установка

pip install pygame

Установка с дополнительными возможностями

pip install pygame[optional]

Проверка установки

import pygame
print(f"Версия Pygame: {pygame.version.ver}")
print(f"Версия SDL: {pygame.version.SDL}")

# Проверка доступных модулей
print("Доступные модули:")
for module in ['display', 'mixer', 'font', 'image']:
    try:
        exec(f"pygame.{module}.get_init()")
        print(f"✓ {module}")
    except:
        print(f"✗ {module}")

Архитектура Pygame

Основные модули

Pygame состоит из нескольких ключевых модулей, каждый из которых отвечает за определённую функциональность:

  • pygame.display — управление окном и экраном
  • pygame.event — обработка событий (клавиатура, мышь)
  • pygame.image — загрузка и обработка изображений
  • pygame.mixer — работа со звуком и музыкой
  • pygame.font — рендеринг текста
  • pygame.sprite — система спрайтов и групп
  • pygame.time — управление временем и частотой кадров
  • pygame.math — математические операции для игр

Базовая настройка и инициализация

Простейшая инициализация

import pygame
import sys

# Инициализация всех модулей pygame
pygame.init()

# Создание окна
screen = pygame.display.set_mode((800, 600))
pygame.display.set_caption("Моя первая игра на Pygame")

# Создание объекта для контроля времени
clock = pygame.time.Clock()

print("Pygame успешно инициализирован!")

Расширенная инициализация с проверкой ошибок

import pygame
import sys

def init_pygame():
    """Инициализация pygame с проверкой ошибок"""
    try:
        pygame.init()
        
        # Проверка инициализации отдельных модулей
        if not pygame.get_init():
            raise Exception("Pygame не инициализирован")
            
        if not pygame.display.get_init():
            raise Exception("Модуль display не инициализирован")
            
        print("Pygame инициализирован успешно")
        return True
        
    except Exception as e:
        print(f"Ошибка инициализации: {e}")
        return False

# Использование
if init_pygame():
    screen = pygame.display.set_mode((800, 600))
    pygame.display.set_caption("Игра инициализирована")

Главный игровой цикл

Сердце любой игры на Pygame — это главный игровой цикл. Он отвечает за обработку событий, обновление игровой логики и отрисовку кадров.

Базовый игровой цикл

import pygame

pygame.init()
screen = pygame.display.set_mode((800, 600))
pygame.display.set_caption("Базовый игровой цикл")
clock = pygame.time.Clock()

# Цвета
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)

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_ESCAPE:
                running = False

    # Обновление игровой логики
    # (здесь будет код обновления позиций объектов)

    # Отрисовка
    screen.fill(BLACK)  # Очистка экрана
    # (здесь будет код отрисовки объектов)
    pygame.display.flip()  # Обновление экрана

    # Контроль FPS
    clock.tick(60)

pygame.quit()
sys.exit()

Улучшенный игровой цикл с состояниями

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:
            # Обновление игры только если не на паузе
            pass

        # Отрисовка
        screen.fill((0, 0, 0))
        if game_state.paused:
            font = pygame.font.SysFont("Arial", 48)
            text = font.render("ПАУЗА", True, (255, 255, 255))
            screen.blit(text, (350, 275))
            
        pygame.display.flip()
        clock.tick(game_state.fps)

    pygame.quit()

Работа с графикой и изображениями

Загрузка и отображение изображений

import pygame
import os

def load_image(filename, convert_alpha=True):
    """Безопасная загрузка изображения с обработкой ошибок"""
    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"Не удалось загрузить изображение {filename}: {e}")
        # Создаём заглушку
        image = pygame.Surface((32, 32))
        image.fill((255, 0, 255))  # Розовый цвет для отсутствующих текстур
        return image

# Пример использования
pygame.init()
screen = pygame.display.set_mode((800, 600))

# Загрузка изображений
player_image = load_image("player.png")
background = load_image("background.jpg", False)

# Отображение
screen.blit(background, (0, 0))
screen.blit(player_image, (100, 100))
pygame.display.flip()

Трансформации изображений

import pygame
import math

def transform_image_demo():
    pygame.init()
    screen = pygame.display.set_mode((800, 600))
    clock = pygame.time.Clock()
    
    # Создаём тестовое изображение
    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
        
        # Анимация трансформаций
        angle += 2
        scale_factor = 1.0 + 0.5 * math.sin(pygame.time.get_ticks() / 1000.0)
        
        # Применение трансформаций
        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)
        
        # Отрисовка
        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()

Работа с текстом и шрифтами

Основы работы с текстом

import pygame

def text_demo():
    pygame.init()
    screen = pygame.display.set_mode((800, 600))
    
    # Различные способы создания шрифтов
    default_font = pygame.font.Font(None, 36)  # Шрифт по умолчанию
    system_font = pygame.font.SysFont("Arial", 48)  # Системный шрифт
    
    # Попытка загрузить пользовательский шрифт
    try:
        custom_font = pygame.font.Font("custom_font.ttf", 24)
    except:
        custom_font = default_font
    
    # Создание текстовых поверхностей
    texts = [
        (default_font.render("Шрифт по умолчанию", True, (255, 255, 255)), (50, 50)),
        (system_font.render("Системный Arial", True, (255, 255, 0)), (50, 100)),
        (custom_font.render("Пользовательский шрифт", True, (0, 255, 255)), (50, 200))
    ]
    
    # Текст с обводкой
    outline_text = system_font.render("Текст с обводкой", True, (255, 255, 255))
    outline_shadow = system_font.render("Текст с обводкой", 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))
        
        # Отрисовка обычных текстов
        for text_surface, pos in texts:
            screen.blit(text_surface, pos)
        
        # Отрисовка текста с обводкой
        screen.blit(outline_shadow, (52, 302))  # Тень
        screen.blit(outline_text, (50, 300))    # Основной текст
        
        pygame.display.flip()
        clock.tick(60)
    
    pygame.quit()

Динамический текст и анимация

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):
        """Создаёт текст с радужным эффектом"""
        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_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()
        
        # Пульсирующий текст
        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()

Обработка событий и пользовательский ввод

Комплексная обработка событий

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):
        # Сброс состояний "только что"
        self.keys_just_pressed.clear()
        self.keys_just_released.clear()
        self.mouse_just_clicked = [False, False, False]
        
        # Обработка событий
        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]

# Пример использования
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)
        
        # Управление движением
        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
        
        # Действия на одиночное нажатие
        if input_handler.is_key_just_pressed(pygame.K_SPACE):
            print("Пробел нажат!")
        
        if input_handler.is_mouse_clicked():
            print(f"Клик мыши в позиции: {input_handler.mouse_pos}")
        
        # Отрисовка
        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()

Работа со звуком и музыкой

Базовая аудиосистема

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):
        """Загружает звуковой эффект"""
        try:
            sound = pygame.mixer.Sound(filename)
            sound.set_volume(self.sound_volume)
            self.sounds[name] = sound
            print(f"Звук '{name}' загружен успешно")
        except pygame.error as e:
            print(f"Не удалось загрузить звук {filename}: {e}")
    
    def play_sound(self, name, loops=0):
        """Воспроизводит звуковой эффект"""
        if name in self.sounds:
            self.sounds[name].play(loops)
    
    def load_music(self, filename):
        """Загружает фоновую музыку"""
        try:
            pygame.mixer.music.load(filename)
            pygame.mixer.music.set_volume(self.music_volume)
            print(f"Музыка загружена: {filename}")
        except pygame.error as e:
            print(f"Не удалось загрузить музыку {filename}: {e}")
    
    def play_music(self, loops=-1):
        """Запускает фоновую музыку"""
        pygame.mixer.music.play(loops)
    
    def stop_music(self):
        """Останавливает музыку"""
        pygame.mixer.music.stop()
    
    def pause_music(self):
        """Приостанавливает музыку"""
        pygame.mixer.music.pause()
    
    def unpause_music(self):
        """Возобновляет музыку"""
        pygame.mixer.music.unpause()
    
    def set_music_volume(self, 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):
        """Устанавливает громкость звуков (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)

# Пример использования
def audio_demo():
    pygame.init()
    screen = pygame.display.set_mode((800, 600))
    clock = pygame.time.Clock()
    
    audio = AudioManager()
    
    # Загрузка звуков (замените на реальные файлы)
    # 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))
        
        # Отображение инструкций
        font = pygame.font.SysFont("Arial", 24)
        instructions = [
            "1 - Звук прыжка",
            "2 - Звук монеты", 
            "M - Запустить музыку",
            "S - Остановить музыку"
        ]
        
        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()

Система спрайтов и анимация

Базовый класс спрайта

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)
        
        # Физические свойства
        self.velocity = pygame.math.Vector2(0, 0)
        self.acceleration = pygame.math.Vector2(0, 0)
        self.friction = 0.9
        
    def update(self, dt):
        # Обновление анимации
        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
        
        # Обновление физики
        self.velocity += self.acceleration * dt
        self.velocity *= self.friction
        self.rect.center += self.velocity * dt
        
        # Ограничение экраном
        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):
        # Создаём анимационные кадры (заглушки)
        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()
    
    # Создание групп спрайтов
    all_sprites = pygame.sprite.Group()
    players = pygame.sprite.Group()
    
    # Создание игрока
    player = Player(400, 300)
    all_sprites.add(player)
    players.add(player)
    
    # Создание NPC
    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  # Дельта времени в секундах
        
        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)
        
        # Обновление всех спрайтов
        all_sprites.update(dt)
        
        # Отрисовка
        screen.fill((20, 20, 40))
        all_sprites.draw(screen)
        
        # Отображение информации
        font = pygame.font.SysFont("Arial", 18)
        info_text = font.render(f"Спрайтов: {len(all_sprites)}, FPS: {int(clock.get_fps())}", 
                               True, (255, 255, 255))
        screen.blit(info_text, (10, 10))
        
        pygame.display.flip()
    
    pygame.quit()

Система столкновений

Продвинутая система коллизий

import pygame
import math

class CollisionMixin:
    """Миксин для обработки столкновений"""
    
    def check_collision_with_group(self, group, kill_on_collision=False):
        """Проверка столкновения с группой спрайтов"""
        collided = pygame.sprite.spritecollide(self, group, kill_on_collision)
        return collided
    
    def check_circular_collision(self, other, radius1=None, radius2=None):
        """Проверка круговой коллизии"""
        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):
        """Попиксельная проверка столкновения"""
        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)
        
        # Создание маски для попиксельных коллизий
        self.mask = pygame.mask.from_surface(self.image)
        
        # Физические свойства
        self.velocity = pygame.math.Vector2(0, 0)
        self.mass = width * height / 1000  # Примерная масса
        
    def apply_collision_response(self, other, collision_type="elastic"):
        """Применение физической реакции на столкновение"""
        if collision_type == "elastic":
            # Упругое столкновение
            v1 = self.velocity
            v2 = other.velocity
            m1 = self.mass
            m2 = other.mass
            
            # Упрощённая формула упругого столкновения
            self.velocity = ((m1 - m2) * v1 + 2 * m2 * v2) / (m1 + m2)
            other.velocity = ((m2 - m1) * v2 + 2 * m1 * v1) / (m1 + m2)
            
        elif collision_type == "absorb":
            # Поглощение
            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()
    
    # Создание спрайтов
    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
        
        # Обновление позиций
        for sprite in sprites:
            sprite.rect.center += sprite.velocity * dt
            
            # Отражение от границ
            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))
        
        # Проверка столкновений между спрайтами
        sprite_list = sprites.sprites()
        for i, sprite1 in enumerate(sprite_list):
            for sprite2 in sprite_list[i+1:]:
                if sprite1.rect.colliderect(sprite2.rect):
                    # Применение физической реакции
                    sprite1.apply_collision_response(sprite2, "elastic")
                    
                    # Разделение спрайтов во избежание застревания
                    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
        
        # Отрисовка
        screen.fill((20, 20, 40))
        sprites.draw(screen)
        
        pygame.display.flip()
    
    pygame.quit()

Оптимизация производительности

Методы повышения 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:
            # Правильная оптимизация изображений
            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:
            # Создание оптимизированной поверхности
            self.image = pygame.Surface((32, 32)).convert()
            self.image.fill((255, 100, 100))
        
        self.rect = self.image.get_rect()
        self.rect.center = (x, y)
        
        # Использование Vector2 для более быстрых вычислений
        self.position = pygame.math.Vector2(x, y)
        self.velocity = pygame.math.Vector2(0, 0)
        
        # Флаг для пропуска обновления
        self.dirty = True
        
    def update(self, dt):
        if not self.dirty:
            return
            
        self.position += self.velocity * dt
        self.rect.center = self.position
        
        # Если спрайт не движется, помечаем как "чистый"
        if self.velocity.length() < 0.1:
            self.dirty = False

def optimization_demo():
    pygame.init()
    
    # Оптимизация настроек pygame
    screen = pygame.display.set_mode((800, 600), pygame.DOUBLEBUF | pygame.HWSURFACE)
    pygame.display.set_caption("Оптимизация производительности")
    
    clock = pygame.time.Clock()
    monitor = PerformanceMonitor()
    
    # Создание большого количества оптимизированных спрайтов
    sprites = pygame.sprite.Group()
    for i in range(500):  # Много спрайтов для тестирования
        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)
    
    # Группы для селективного обновления
    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)
    
    # Предварительная отрисовка статичного фона
    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  # Увеличенный лимит FPS
        
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                running = False
            elif event.type == pygame.KEYDOWN:
                if event.key == pygame.K_r:
                    # Перезапуск всех спрайтов
                    for sprite in sprites:
                        sprite.dirty = True
                        sprite.velocity = pygame.math.Vector2(
                            (frame_count % 200 - 100) / 10,
                            (frame_count % 150 - 75) / 10
                        )
        
        # Обновляем только движущиеся спрайты
        moving_sprites.update(dt)
        
        # Проверка границ только для движущихся спрайтов
        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)
        
        # Оптимизированная отрисовка
        if frame_count % 5 == 0:  # Обновляем фон реже
            screen.blit(background, (0, 0))
        
        # Отрисовка только движущихся спрайтов
        moving_sprites.draw(screen)
        
        # Отображение статистики производительности
        if frame_count % 30 == 0:  # Обновляем текст реже
            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))
            
            # Очистка области текста
            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()

Полная таблица методов и функций Pygame

Модуль Функция/Метод Описание Пример использования
pygame init() Инициализирует все модули pygame.init()
  quit() Завершает работу всех модулей pygame.quit()
  get_init() Проверяет инициализацию if pygame.get_init():
display set_mode(size, flags) Создаёт игровое окно screen = pygame.display.set_mode((800, 600))
  set_caption(title) Устанавливает заголовок pygame.display.set_caption("Игра")
  flip() Обновляет весь экран pygame.display.flip()
  update(rect_list) Обновляет части экрана pygame.display.update([rect1, rect2])
  get_surface() Получает поверхность экрана screen = pygame.display.get_surface()
event get() Получает все события events = pygame.event.get()
  poll() Получает одно событие event = pygame.event.poll()
  wait() Ждёт следующее событие event = pygame.event.wait()
  clear() Очищает очередь событий pygame.event.clear()
  post(event) Добавляет событие в очередь pygame.event.post(my_event)
key get_pressed() Состояние всех клавиш keys = pygame.key.get_pressed()
  get_mods() Состояние модификаторов mods = pygame.key.get_mods()
  set_repeat(delay, interval) Настройка повтора клавиш pygame.key.set_repeat(500, 50)
mouse get_pos() Позиция курсора x, y = pygame.mouse.get_pos()
  get_pressed() Состояние кнопок мыши buttons = pygame.mouse.get_pressed()
  set_pos(pos) Устанавливает позицию курсора pygame.mouse.set_pos((400, 300))
  set_visible(bool) Показать/скрыть курсор pygame.mouse.set_visible(False)
image load(filename) Загружает изображение img = pygame.image.load("sprite.png")
  save(surface, filename) Сохраняет изображение pygame.image.save(screen, "screenshot.png")
Surface blit(source, dest) Отрисовывает поверхность screen.blit(sprite, (100, 100))
  fill(color) Заливает цветом surface.fill((255, 0, 0))
  convert() Оптимизирует поверхность sprite = sprite.convert()
  convert_alpha() Оптимизирует с альфа-каналом sprite = sprite.convert_alpha()
  get_rect() Получает прямоугольник rect = surface.get_rect()
  set_alpha(value) Устанавливает прозрачность surface.set_alpha(128)
transform scale(surface, size) Масштабирует изображение scaled = pygame.transform.scale(img, (64, 64))
  rotate(surface, angle) Поворачивает изображение rotated = pygame.transform.rotate(img, 45)
  flip(surface, xbool, ybool) Отражает изображение flipped = pygame.transform.flip(img, True, False)
  rotozoom(surface, angle, scale) Поворот и масштаб transformed = pygame.transform.rotozoom(img, 45, 1.5)
font SysFont(name, size) Создаёт системный шрифт font = pygame.font.SysFont("Arial", 24)
  Font(filename, size) Загружает шрифт из файла font = pygame.font.Font("font.ttf", 24)
  render(text, antialias, color) Рендерит текст text = font.render("Hello", True, (255, 255, 255))
  get_fonts() Список доступных шрифтов fonts = pygame.font.get_fonts()
mixer init() Инициализация звука pygame.mixer.init()
  Sound(filename) Загружает звук sound = pygame.mixer.Sound("sound.wav")
  music.load(filename) Загружает музыку pygame.mixer.music.load("music.mp3")
  music.play(loops) Проигрывает музыку pygame.mixer.music.play(-1)
  music.stop() Останавливает музыку pygame.mixer.music.stop()
  music.pause() Пауза музыки pygame.mixer.music.pause()
  music.set_volume(volume) Громкость музыки pygame.mixer.music.set_volume(0.7)
sprite Sprite() Базовый класс спрайта class Player(pygame.sprite.Sprite):
  Group() Группа спрайтов all_sprites = pygame.sprite.Group()
  spritecollide() Проверка столкновений hits = pygame.sprite.spritecollide(player, enemies, False)
  groupcollide() Столкновения групп hits = pygame.sprite.groupcollide(bullets, enemies, True, True)
time Clock() Объект часов clock = pygame.time.Clock()
  tick(fps) Ограничение FPS clock.tick(60)
  get_ticks() Время с запуска (мс) time = pygame.time.get_ticks()
  delay(ms) Пауза в миллисекундах pygame.time.delay(1000)
  wait(ms) Неблокирующая пауза pygame.time.wait(100)
draw rect(surface, color, rect) Рисует прямоугольник pygame.draw.rect(screen, (255, 0, 0), (10, 10, 50, 50))
  circle(surface, color, center, radius) Рисует круг pygame.draw.circle(screen, (0, 255, 0), (100, 100), 25)
  line(surface, color, start, end) Рисует линию pygame.draw.line(screen, (255, 255, 255), (0, 0), (100, 100))
  polygon(surface, color, points) Рисует многоугольник pygame.draw.polygon(screen, (0, 0, 255), [(10,10), (50,10), (30,50)])
math Vector2(x, y) 2D вектор velocity = pygame.math.Vector2(5, 3)
  Vector3(x, y, z) 3D вектор position = pygame.math.Vector3(10, 20, 30)
mask from_surface(surface) Создаёт маску из поверхности mask = pygame.mask.from_surface(sprite)
  overlap(mask, offset) Проверяет пересечение масок overlap = mask1.overlap(mask2, (0, 0))
gfxdraw filled_circle() Закрашенный круг с AA pygame.gfxdraw.filled_circle(screen, x, y, r, color)
  bezier() Кривая Безье pygame.gfxdraw.bezier(screen, points, steps, color)

Создание полноценной игры: Пример "Астероиды"

import pygame
import math
import random

class GameObject:
    """Базовый класс для всех игровых объектов"""
    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
        
        # Циклический экран
        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):
        # Поворот
        self.angle += self.rotation_speed * dt
        
        # Тяга
        if self.thrust > 0:
            thrust_vector = pygame.math.Vector2(0, -self.thrust)
            thrust_vector.rotate_ip(self.angle)
            self.velocity += thrust_vector * dt
        
        # Трение
        self.velocity *= 0.99
        
        super().update(dt)
        
        # Обновление пуль
        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):
        # Вершины корабля
        points = [
            pygame.math.Vector2(0, -self.radius),
            pygame.math.Vector2(-self.radius//2, self.radius),
            pygame.math.Vector2(self.radius//2, self.radius)
        ]
        
        # Поворот и смещение
        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)
        
        # Рисуем пули
        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  # секунды
        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)
        
        # Случайная скорость
        speed = random.uniform(50, 150)
        angle = random.uniform(0, 360)
        self.velocity = pygame.math.Vector2(speed, 0)
        self.velocity.rotate_ip(angle)
        
        # Генерация неровной формы
        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):
        # Поворот и смещение вершин
        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):
        """Разделение астероида на более мелкие"""
        if self.size > 1:
            fragments = []
            for _ in range(2):
                fragment = Asteroid(self.position.x, self.position.y, self.size - 1)
                # Добавляем случайную скорость к осколкам
                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("Астероиды")
        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
        
        # Создание начальных астероидов
        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 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
        
        # Стрельба по пробелу (с ограничением частоты)
        if keys[pygame.K_SPACE]:
            self.ship.shoot()
    
    def update(self, dt):
        if self.game_over:
            return
        
        # Обновление корабля
        self.ship.update(dt)
        
        # Обновление астероидов
        for asteroid in self.asteroids:
            asteroid.update(dt)
        
        # Проверка столкновений пуль с астероидами
        for bullet in self.ship.bullets[:]:
            for asteroid in self.asteroids[:]:
                if bullet.check_collision(asteroid):
                    # Удаление пули и астероида
                    self.ship.bullets.remove(bullet)
                    self.asteroids.remove(asteroid)
                    
                    # Увеличение счёта
                    self.score += (4 - asteroid.size) * 100
                    
                    # Разделение астероида
                    fragments = asteroid.split()
                    self.asteroids.extend(fragments)
                    break
        
        # Проверка столкновения корабля с астероидами
        for asteroid in self.asteroids:
            if self.ship.check_collision(asteroid):
                self.lives -= 1
                if self.lives <= 0:
                    self.game_over = True
                else:
                    # Перемещение корабля в безопасное место
                    self.ship.position = pygame.math.Vector2(400, 300)
                    self.ship.velocity = pygame.math.Vector2(0, 0)
                break
        
        # Проверка победы (все астероиды уничтожены)
        if not self.asteroids:
            # Создание нового уровня
            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:
            # Рисуем корабль
            self.ship.draw(self.screen)
            
            # Рисуем астероиды
            for asteroid in self.asteroids:
                asteroid.draw(self.screen)
        
        # Интерфейс
        score_text = self.font.render(f"Счёт: {self.score}", True, (255, 255, 255))
        lives_text = self.font.render(f"Жизни: {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("ИГРА ОКОНЧЕНА! Нажмите R для перезапуска", 
                                            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
        
        # Создание начальных астероидов
        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()

# Запуск игры
if __name__ == "__main__":
    game = Game()
    game.run()

Часто задаваемые вопросы

Как оптимизировать производительность игры на Pygame?

Для оптимизации производительности в Pygame используйте следующие методы:

  • Применяйте convert() и convert_alpha() к загруженным изображениям
  • Используйте pygame.sprite.Group() для эффективного управления спрайтами
  • Ограничивайте частоту обновления экрана с помощью clock.tick()
  • Избегайте создания новых объектов в главном цикле
  • Используйте pygame.display.update(rect_list) вместо flip() для частичного обновления

Как добавить звук в игру?

Для работы со звуком инициализируйте модуль mixer и загружайте звуковые файлы:

pygame.mixer.init()
sound_effect = pygame.mixer.Sound("sound.wav")
sound_effect.play()

# Для фоновой музыки
pygame.mixer.music.load("background.mp3")
pygame.mixer.music.play(-1)  # -1 для бесконечного повтора

Как создать меню в игре?

Новости