Прокручиваемые фоны и параллакс в Pygame

Прокручиваемые фоны повышают визуальную глубину игры и создают ощущение движения в мире. Их реализация в Pygame несложна: достаточно регулярно смещать фоновые слои по оси X (или Y) и при необходимости перезапускать их позицию для бесконечной прокрутки.
Краткое определение: параллакс — это приём, когда слои сцены движутся с разной скоростью, создавая иллюзию глубины.
Что вы получите из этого руководства
- Пошаговые рабочие примеры: simple-game.py, scrolling-bg.py, parallax.py.
- Как быстро перейти от однослойного фона к многослойной параллакс-сцене с картинками.
- Набор практических советов по производительности, масштабированию и тестированию.
- Чек-листы ролей (разработчик, художник, тестировщик), мерч-схему принятия решений и примеры тест-кейсов.
Зависимости и подготовка
- Убедитесь, что установлен Python 3.7+ и Pygame (pip install pygame).
- Определите разрешение окна и целевые устройства. В коде ниже используются переменные screen_width и screen_height.
Важно: при использовании изображений заранее подготовьте версии, масштабированные под целевые разрешения (см. раздел «Оптимизация»).
Простая игра: перед тем как добавить фон
Сначала создадим минимальную игровую сцену, где игрок может ходить влево и вправо, а также есть две платформы — прямоугольники, которые служат окружением. Создайте файл simple-game.py и поместите туда следующий код.
# simple-game.py
import pygame
import sys
pygame.init()
screen_width, screen_height = 800, 450
screen = pygame.display.set_mode((screen_width, screen_height))
clock = pygame.time.Clock()
# Игрок
player_x, player_y = 100, screen_height - 150
player_w, player_h = 32, 48
player_speed = 4
# Платформы
rect1 = pygame.Rect(50, screen_height - 100, 200, 10)
rect2 = pygame.Rect(screen_width - 250, screen_height - 200, 200, 10)
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
keys = pygame.key.get_pressed()
if keys[pygame.K_LEFT] and player_x > 0:
player_x -= player_speed
if keys[pygame.K_RIGHT] and player_x < screen_width - player_w:
player_x += player_speed
screen.fill((30, 30, 30))
pygame.draw.rect(screen, (0, 255, 0), rect1)
pygame.draw.rect(screen, (0, 255, 0), rect2)
pygame.draw.rect(screen, (255, 200, 0), (player_x, player_y, player_w, player_h))
pygame.display.flip()
clock.tick(60)
pygame.quit()
sys.exit()В этом примере фон просто заливается цветом, а платформы и игрок отрисовываются над ним. Следующий шаг — превратить однотонный фон в прокручиваемый.
Несколько фонов: модель слоёв
Идея простая: создайте список фоновых слоёв, каждому слою назначьте цвет (или изображение) и скорость. Быстрые слои находятся ближе к камере; медленные — дальше.
Пример определения прямоугольных фоновых слоёв:
background_layers = [
pygame.Rect(0, 0, screen_width, screen_height),
pygame.Rect(0, 0, screen_width, screen_height)
]
background_colors = [(30, 30, 30), (60, 60, 60)]
background_speeds = [0.1, 1.0]Каждый элемент background_layers покрывает весь экран. Скорости задают относительное смещение по кадру. Значения скоростей подбирайте эмпирически: слои заднего плана — малые дробные значения, переднего плана — большие целые.
Прокрутка фона: логика обновления
В игровом цикле обновляйте координаты слоёв и перерисовывайте их. Классический приём для непрерывной прокрутки — иметь две копии слоя (или сбрасывать координату при достижении границы).
for i in range(len(background_layers)):
background_layers[i].x -= background_speeds[i]
if background_layers[i].x <= -screen_width:
background_layers[i].x = 0
pygame.draw.rect(screen, background_colors[i], background_layers[i])Объяснение: каждый кадр сдвигаем слой влево. Когда его левый край ушёл за предел экрана, возвращаем x к 0. В простейшей схеме это работает, если слой покрывает весь экран и выглядит непрерывно.
Параллакс при движении игрока
Чтобы параллакс выглядел более натурально, изменяйте движение платформ и слоёв только когда игрок двигается. Это создаёт впечатление, что мир «движется вокруг» героя.
Пример структуры платформ со скоростью и обновлением в цикле:
# Определение платформ
rect1 = pygame.Rect(50, screen_height - 100, 200, 10)
rect2 = pygame.Rect(screen_width - 250, screen_height - 200, 200, 10)
platforms = [
{"rect": rect1, "speed": 3},
{"rect": rect2, "speed": 1}
]
# В игровом цикле
keys = pygame.key.get_pressed()
if keys[pygame.K_LEFT] and player_x > 0:
player_x -= player_speed
for platform in platforms:
platform["rect"].x -= platform["speed"]
if keys[pygame.K_RIGHT] and player_x < screen_width:
player_x += player_speed
for platform in platforms:
platform["rect"].x += platform["speed"]
for platform in platforms:
pygame.draw.rect(screen, (0, 255, 0), platform["rect"])В такой схеме платформы двигаются относительно игрока. Аналогично можно двигать и фоновые слои с разной амплитудой.
Использование изображений вместо однотонных прямоугольников
Изображения дают богатую детализацию, но требуют внимания к производительности и памяти. Основные шаги:
- Загрузить изображение: pygame.image.load(…).convert()/convert_alpha().
- Подогнать размер: pygame.transform.scale(img, (screen_width, screen_height)).
- В цикле использовать screen.blit(image, rect).
Пример подготовки и масштабирования:
background_images = [
pygame.image.load("background_0.png").convert(),
pygame.image.load("background_1.png").convert(),
pygame.image.load("background_2.png").convert()
]
background_speeds = [1, 2, 3]
for i in range(len(background_images)):
size = (screen_width, screen_height)
background_images[i] = pygame.transform.scale(background_images[i], size)И отрисовка (вместо pygame.draw.rect):
for i in range(len(background_layers)):
background_layers[i].x -= background_speeds[i]
if background_layers[i].x <= -screen_width:
background_layers[i].x = 0
screen.blit(background_images[i], background_layers[i])Совет: если фон создаётся из плиток (тайлов), используйте повторяющееся смещение и отрисовывайте столько копий, сколько нужно для покрытия экрана при любом смещении.
Дополнительные функции и идеи
Ниже — набор практических расширений, которые легко интегрировать.
Случайные цвета фонов
Если вам хватает однотонных фонов, можно каждый запуск генерировать случайные RGB-цвета:
import random
background_colors = [
(random.randint(0, 255), random.randint(0, 255), random.randint(0, 255)),
(random.randint(0, 255), random.randint(0, 255), random.randint(0, 255)),
(random.randint(0, 255), random.randint(0, 255), random.randint(0, 255))
]Добавление большего числа слоёв
Для богатой сцены добавьте 3–6 слоёв с возрастающими скоростями. Обычно задний план — дробные значения, средний — 1–3, передний — 3 и выше.
Слои с горизонтальной и вертикальной прокруткой
Прокрутку можно комбинировать: задний план медленно движется по Y (плывущие облака), средний — по X (города), передний — по X быстрее (труды в поле зрения).
Лучшие практики и оптимизация
Важно держать игру плавной и отзывчивой. Вот ключевые рекомендации:
- Используйте convert()/convert_alpha() для подготавливаемых изображений.
- Масштабируйте изображения один раз при загрузке, а не каждый кадр.
- Ограничьте количество дорогостоящих операций в главном цикле: математические расчёты хранят в отдельных переменных.
- Рассмотрите технику dirty rect для частичной перерисовки, если сцена статична.
- Для мобильных устройств подберите меньшие текстуры и более низкие FPS, если нужно.
Важно: отрисовка большого количества прозрачных изображений (alpha) может сильно замедлить рендер; по возможности предварительно рендерьте композицию слоёв на задний план.
Тестирование и критерии приёмки
Критерии приёмки:
- Фон прокручивается плавно без заметных «скачков» при постоянной скорости.
- Параллакс корректно работает при движении в обе стороны.
- При смене разрешения экран заполняется корректно (без чёрных полос) или адаптируется выбранной стратегией.
- Потребление памяти и FPS соответствуют целевым платформам.
Примеры тест-кейсов:
- Запустить игру на 800×600, 1280×720, 1920×1080 — проверить покрытие экрана.
- Загрузить высокое разрешение фона на целевом устройстве и замерить FPS под нагрузкой.
- Проверить, что при достижении границы x <= -screen_width фон «зацикливается» без пропусков.
Чек-лист ролей
Разработчик:
- Подключил convert() к изображениям
- Не масштабирует текстуры каждый кадр
- Ограничил скорость слоя для адекватной читаемости
Художник:
- Подготовил изображения с запасом по краям (bleed) для безопасной обрезки
- Предоставил версии под разные соотношения сторон
Тестировщик:
- Проверил на гориз./вертик. движении
- Измерил FPS и проверил утечки памяти
Модель принятия решения (flowchart)
Если вы сомневаетесь, использовать ли однотонные слои, тайлы или полноразмерные изображения, следуйте этой упрощённой схеме:
flowchart TD
A[Нужен фон] --> B{Требуется детализация}
B -- Низкая --> C[Однотонные слои / градиенты]
B -- Средняя --> D[Тайловые изображения]
B -- Высокая --> E[Полноплатные фоновые изображения]
D --> F{Память / Производительность}
E --> F
F -- Ограничена --> G[Уменьшить разрешение / использовать LOD]
F -- Достаточна --> H[Использовать выбранный подход]Когда прокрутка даёт сбои (контрпримеры)
- Фон с незамкнутой текстурой: при простом сбросе x = 0 появится заметный стык.
- Неверный порядок отрисовки: слои ближе к камере отрисованы раньше — они будут спрятаны за задними.
- Большое количество полупрозрачных слоёв без предварительного бэка — резкое падение FPS.
Советы по совместимости и миграции
- Если вы планируете портировать код на другую библиотеку (например, SDL2 напрямую или Godot), держите логику прокрутки отделённой от отрисовки — тогда переезд будет проще.
- Для Web (через Emscripten/pyodide) используйте маленькие версии текстур и осторожно тестируйте потребление памяти.
Краткий набор полезных сниппетов (cheat sheet)
- Загрузить и подготовить изображение:
img = pygame.image.load("bg.png").convert()
img = pygame.transform.scale(img, (screen_width, screen_height))- Движение слоя с зацикливанием (две копии для корректной отрисовки):
x -= speed
if x <= -screen_width:
x += screen_width
screen.blit(img, (x, 0))
screen.blit(img, (x + screen_width, 0))- Сглаживание движения (delta time):
dt = clock.tick(60) / 1000.0 # секунды
x -= speed * dtИспользование dt позволяет одинаково ощущать скорость на разных частотах кадров.
Риски и способы смягчения
- Риск: падение FPS при большом количестве изображений. Смягчение: уменьшить размер текстур, использовать LOD, комбинировать слои.
- Риск: сильный расход памяти. Смягчение: хранить несколько размеров текстур, загружать по требованию.
Короткий глоссарий (1 строка на термин)
- Параллакс — визуальный эффект, когда слои движутся с разной скоростью для передачи глубины.
- Blit — операция копирования поверхности изображения на экран (Pygame).
- convert() — метод Pygame для ускорения рендеринга изображения на целевой поверхности.
Итог и рекомендации
Прокручиваемые фоны и параллакс — мощный инструмент для оживления 2D-мира. Начните с простых однотонных слоёв, затем добавьте ещё слоёв и/или изображения. Обязательно профилируйте производительность и готовьте несколько уровней качества текстур для разных платформ. Экспериментируйте с комбинациями скоростей: маленькая разница даёт спокойный фон, большая — ощущение высокой скорости движения.
Дополнительная рекомендация: поддерживайте читаемость — слишком быстрый фронтальный слой может мешать восприятию игрового процесса.
Спасибо за чтение — пробуйте, профилируйте и настраивайте под вашу игру.