Разработка игр на 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 для бесконечного повтора
Настоящее и будущее развития ИИ: классической математики уже недостаточно
Эксперты предупредили о рисках фейковой благотворительности с помощью ИИ
В России разработали универсального ИИ-агента для роботов и индустриальных процессов