Меню запуска и экран «Игра окончена» в PyGame
В этой статье показано, как добавить стартовое меню и экран «Игра окончена» в простую игру на PyGame. Приведён исправленный и расширенный пример кода с управлением состояниями, обработкой ввода клавиатуры, перезапуском и базовыми советами по UI и тестированию.

Зачем разделять состояния игры
Разделение логики игры на состояния (start, game, game_over) упрощает код, делает поведение предсказуемым и облегчает ввод новых экранов: настроек, паузы, таблицы рекордов. Это базовый паттерн для большинства 2D-игр.
Краткое определение терминов
- Состояние игры: текущий экран или режим работы (например, стартовое меню, игровой процесс, экран конца игры).
- Рендер: отрисовка объектов на экране.
Создание простой игры — минимальная версия
Перед добавлением меню полезно иметь простейшую игру для проверки взаимодействия. Ниже сниппет, адаптированный под Python/PyGame, с минимальной логикой: игрок-куб, препятствие-куб, детекция столкновений.
import pygame
pygame.init()
screen_width = 750
screen_height = 450
screen = pygame.display.set_mode((screen_width, screen_height))
obstacle_x = 400
obstacle_y = 400
obstacle_w = 40
obstacle_h = 40
player_x = 200
player_y = 400
player_w = 20
player_h = 20
clock = pygame.time.Clock()
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]:
player_x -= 5
if keys[pygame.K_RIGHT]:
player_x += 5
# Коллизия
if (player_x + player_w > obstacle_x and player_x < obstacle_x + obstacle_w
and player_y + player_h > obstacle_y and player_y < obstacle_y + obstacle_h):
# В этой упрощённой версии можно просто выйти или обработать состояние
running = False
screen.fill((0, 0, 0))
pygame.draw.rect(screen, (255, 0, 0), (obstacle_x, obstacle_y, obstacle_w, obstacle_h))
pygame.draw.rect(screen, (0, 255, 0), (player_x, player_y, player_w, player_h))
pygame.display.update()
clock.tick(60)
pygame.quit()Важно: этот фрагмент демонстрационный. В реальном проекте желательно вынести логические блоки в функции или классы.
Стартовое меню — что отображать
Стартовое меню должно быть простым и информативным: название игры, краткая инструкция по управлению, кнопка “Старт” или подсказка клавиши. Важно обеспечить явный фокус: что нажать для начала.
Пример функции отрисовки стартового меню (строки локализованы на русский):
def draw_start_menu(screen, screen_w, screen_h, font):
screen.fill((0, 0, 0))
title = font.render('Моя игра', True, (255, 255, 255))
hint = font.render('Пробел — начать', True, (200, 200, 200))
screen.blit(title, (screen_w/2 - title.get_width()/2, screen_h/2 - title.get_height()))
screen.blit(hint, (screen_w/2 - hint.get_width()/2, screen_h/2 + 10))
pygame.display.update()Чтобы меню выглядело аккуратно, используйте читабельный шрифт и достаточный контраст текста и фона.
Экран «Игра окончена» — что добавить
Экран конца игры обычно показывает причину завершения, очки и варианты: перезапустить либо выйти. Важно дать пользователю очевидный путь дальше.
Пример функции экрана конца игры:
def draw_game_over(screen, screen_w, screen_h, font, score=None):
screen.fill((0, 0, 0))
title = font.render('Игра окончена', True, (255, 255, 255))
restart = font.render('R — перезапуск', True, (200, 200, 200))
quit_text = font.render('Q — выход', True, (200, 200, 200))
screen.blit(title, (screen_w/2 - title.get_width()/2, screen_h/2 - title.get_height()))
if score is not None:
score_text = font.render(f'Очки: {score}', True, (255, 255, 0))
screen.blit(score_text, (screen_w/2 - score_text.get_width()/2, screen_h/2))
screen.blit(restart, (screen_w/2 - restart.get_width()/2, screen_h/2 + 40))
screen.blit(quit_text, (screen_w/2 - quit_text.get_width()/2, screen_h/2 + 80))
pygame.display.update()Полный улучшенный пример: состояние, сброс, защита от ошибок
Ниже полный рабочий пример с исправлениями: введён флаг running, корректная инициализация game_over, таймер кадра, функция сброса состояния, аккуратная обработка клавиш и явная установка состояний.
import pygame
pygame.init()
screen_w = 750
screen_h = 450
screen = pygame.display.set_mode((screen_w, screen_h))
font = pygame.font.SysFont('arial', 36)
clock = pygame.time.Clock()
# Игровые параметры
obstacle = {'x': 400, 'y': 400, 'w': 40, 'h': 40}
player = {'x': 200, 'y': 400, 'w': 20, 'h': 20}
# Состояния: 'start', 'game', 'game_over'
game_state = 'start'
score = 0
running = True
def reset_game():
player['x'] = 200
player['y'] = 400
# При необходимости: менять положение препятствия, очки и т.д.
global score
score = 0
def draw_start():
draw_start_menu(screen, screen_w, screen_h, font)
def draw_start_menu(screen, screen_w, screen_h, font):
screen.fill((10, 10, 30))
title = font.render('Моя игра', True, (255, 255, 255))
hint = font.render('Пробел — начать', True, (200, 200, 200))
screen.blit(title, (screen_w/2 - title.get_width()/2, screen_h/2 - 60))
screen.blit(hint, (screen_w/2 - hint.get_width()/2, screen_h/2))
pygame.display.update()
def draw_game():
screen.fill((0, 0, 0))
pygame.draw.rect(screen, (255, 0, 0), (obstacle['x'], obstacle['y'], obstacle['w'], obstacle['h']))
pygame.draw.rect(screen, (0, 255, 0), (player['x'], player['y'], player['w'], player['h']))
# Простейший счет
score_text = font.render(f'Очки: {score}', True, (255, 255, 255))
screen.blit(score_text, (10, 10))
pygame.display.update()
def draw_game_over_screen():
draw_game_over(screen, screen_w, screen_h, font, score)
def draw_game_over(screen, screen_w, screen_h, font, score=None):
screen.fill((30, 0, 0))
title = font.render('Игра окончена', True, (255, 255, 255))
restart = font.render('R — перезапуск', True, (200, 200, 200))
quit_text = font.render('Q — выход', True, (200, 200, 200))
screen.blit(title, (screen_w/2 - title.get_width()/2, screen_h/2 - 60))
if score is not None:
score_text = font.render(f'Очки: {score}', True, (255, 255, 0))
screen.blit(score_text, (screen_w/2 - score_text.get_width()/2, screen_h/2 - 10))
screen.blit(restart, (screen_w/2 - restart.get_width()/2, screen_h/2 + 30))
screen.blit(quit_text, (screen_w/2 - quit_text.get_width()/2, screen_h/2 + 70))
pygame.display.update()
# Инициалный сброс
reset_game()
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
keys = pygame.key.get_pressed()
if game_state == 'start':
draw_start()
if keys[pygame.K_SPACE]:
reset_game()
game_state = 'game'
elif game_state == 'game':
# Управление игроком
if keys[pygame.K_LEFT]:
player['x'] -= 5
if keys[pygame.K_RIGHT]:
player['x'] += 5
# Простейшая логика роста очков со временем
score += 1
# Проверка столкновения
if (player['x'] + player['w'] > obstacle['x'] and player['x'] < obstacle['x'] + obstacle['w']
and player['y'] + player['h'] > obstacle['y'] and player['y'] < obstacle['y'] + obstacle['h']):
game_state = 'game_over'
draw_game()
elif game_state == 'game_over':
draw_game_over_screen()
if keys[pygame.K_r]:
game_state = 'start'
if keys[pygame.K_q]:
running = False
clock.tick(60)
pygame.quit()Этот вариант устраняет несколько общих ошибок: неопределённые флаги, некорректные ветвления в цикле, отсутствие лимита FPS, отсутствие функции сброса состояния.
Советы по улучшению UI и взаимодействия
- Шрифт и читаемость: выбирайте моно/без засечек шрифты с хорошим размером для основного текста.
- Обратная связь: небольшая анимация, звук или вспышка при столкновении усиливают впечатление.
- Доступность: поддержка клавиатуры и мыши, понятные подсказки.
- Кликабельные кнопки: сделайте область кнопки и обработку mousebutton для удобства на тач/мыши.
Важно: тестируйте UI на разных разрешениях и соотношениях сторон.
Варианты и когда предложенный подход не подходит
- Когда это крупный проект: для больших игр лучше использовать архитектуру “сцен” или готовый фреймворк (например, Godot) с системой сцен и сигналов.
- Для сложной логики и анимаций: переходите к объектно-ориентированному подходу, ECS или сторонним менеджерам состояний.
Полезные альтернативы и паттерны
- Scene manager: отдельный класс, который переключает сцены и хранит стек экранов.
- FSM (Finite State Machine): явная машина состояний для управления переходами.
- Компонентный подход: объекты с компонентами для рендеринга, физики и ввода.
Ментальные модели и heuristics
- Один экран — одна функция/класс: упрощает тестирование.
- Явные переходы состояний: избегайте скрытых побочных эффектов при смене состояния.
- Инициализация до запуска: заранее подготовьте ресурсы (шрифты, звуки), чтобы избежать лагов.
Мини-процесс разработки
- Реализовать минимальную игровую петлю и базовую механику.
- Добавить устройство состояний (start, game, game_over).
- Сделать функции отрисовки для каждого состояния.
- Обработать ввод клавиатуры и мыши.
- Добавить сброс состояния и проверку граничных условий.
- Протестировать и собрать отзывы.
Критерии приёмки
- При старте приложения показывается стартовое меню.
- При нажатии пробела игра начинается и управляется стрелками влево/вправо.
- При столкновении появляется экран «Игра окончена» с подсказками R и Q.
- Клавиша R возвращает в стартовое меню, Q корректно завершает приложение.
- FPS стабилен (например, ~60) и нет утечек событий.
Набор тестов и приемочные сценарии
- Нажать пробел на стартовом экране: ожидается переход в игровой режим.
- Во время игры нажать стрелки: игрок должен сдвигаться по X.
- Создать искусственную коллизию: ожидается переход в game_over.
- На экране конца игры нажать R: возвращаемся в старт.
- На экране конца игры нажать Q: приложение закрывается.
Быстрый чек-лист для релиза
- Шрифты и текст читаемы на целевом разрешении
- Кнопки интерактивны клавиатурой и мышью
- Есть обработка закрытия окна
- Таймер кадров установлен
- Имеется функция сброса состояния
Примеры расширений (идеи для следующего шага)
- Добавить меню опций (звук, управление).
- Сделать кнопку мышью и анимацию наведения.
- Добавить таблицу рекордов с локальным сохранением.
Короткое резюме
Разделение игры на понятные состояния упрощает разработку и тестирование. В статье представлен рабочий и улучшенный шаблон с стартовым меню, игровым процессом и экраном «Игра окончена», а также рекомендации по UI, тестам и дальнейшему развитию.
Похожие материалы
Включить гибернацию в Windows 11 — инструкция
Следите за ЧМ-2022 через Google на Android
Что ждать от презентации Google: Pixel 8 и Pixel Watch 2
Переименование фото по дате съёмки — Namexif и Stamp
Шифрование и защита смартфона: простые шаги