Создание классических аркадных игр — один из лучших способов применить теоретические знания Python на практике. В этом руководстве мы с нуля разработаем игру «Змейка», используя библиотеку Pygame
— стандартный инструмент для 2D-разработки в экосистеме Python.
Статья рассчитана на читателей, уже знакомых с базовым синтаксисом Python, и ставит целью дать четкий, пошаговый алгоритм для создания законченного проекта. По завершении вы не только получите готовую игру, но и освоите ключевые принципы, которые сможете применить при разработке собственных, более сложных приложений.
Раздел 1: Подготовка и настройка проекта
Прежде чем написать первую строку игрового кода, необходимо создать чистое и изолированное рабочее окружение. Это стандартная практика в Python-разработке, которая предотвращает конфликты между зависимостями разных проектов. Мы также определим базовые параметры нашего будущего приложения: размер окна, цвета и скорость обновления.
Шаг 1: Создание виртуального окружения
Сначала создадим папку для нашего проекта и перейдем в нее через терминал или командную строку.
mkdir snake_project
cd snake_project
Теперь внутри этой папки создадим виртуальное окружение с помощью встроенного модуля venv
. Мы назовем его venv
.
python -m venv venv
Эта команда создаст директорию venv
, в которой будут храниться интерпретатор Python и все библиотеки, относящиеся только к этому проекту. Чтобы начать им пользоваться, окружение нужно активировать.
-
Для Windows:
venv\Scripts\activate
-
Для macOS и Linux:
source venv/bin/activate
После успешной активации вы увидите (venv)
в начале командной строки. Это означает, что вы работаете в изолированной среде.

Шаг 2: Установка Pygame
Теперь, когда окружение активно, можно безопасно установить Pygame. Библиотека будет установлена только для нашего проекта, не затрагивая глобальную систему.
pip install pygame
Шаг 3: Создание игрового окна и базовых констант
Внутри папки проекта (snake_project
) создайте файл snake_game.py
. Откройте его в вашем редакторе кода и добавьте стартовый шаблон. Он будет содержать импорты, инициализацию Pygame, определение основных констант и создание игрового окна.
import pygame
# 1. Инициализация Pygame
pygame.init()
# 2. Определение констант
# Размеры окна
SCREEN_WIDTH = 640
SCREEN_HEIGHT = 480
# Размер одного блока змейки и еды
BLOCK_SIZE = 20
# Цвета (RGB)
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
RED = (255, 0, 0)
GREEN = (0, 128, 0)
# 3. Создание игрового окна
screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
pygame.display.set_caption('Змейка на Pygame')
# 4. Настройка для контроля FPS
clock = pygame.time.Clock()
Давайте разберем, что делает этот код:
pygame.init()
: Запускает все необходимые модули Pygame. Эту функцию нужно вызывать в самом начале.Константы: Мы выносим в переменные с заглавными буквами значения, которые не будут меняться в ходе игры. Это улучшает читаемость кода и упрощает его изменение в будущем. Например, если вы захотите увеличить игровое поле, достаточно будет поменять значения
SCREEN_WIDTH
иSCREEN_HEIGHT
в одном месте.pygame.display.set_mode()
: Эта функция создает главное окно (поверхность), на которой будет происходить отрисовка. Мы передаем ей кортеж с шириной и высотой.pygame.time.Clock()
: Создает специальный объект, который поможет нам управлять скоростью игры, ограничивая количество кадров в секунду (FPS). Это гарантирует, что игра будет работать с одинаковой скоростью на компьютерах разной мощности.
На этом этапе у нас есть готовая основа для проекта: настроенное окружение и код, который создает пустое черное окно с заголовком "Змейка на Pygame".
Раздел 2: Создание игровых объектов (Змейка и Еда)
Теперь, когда у нас есть «сцена» в виде игрового окна, пора добавить на нее «актеров»: змейку, которой будет управлять игрок, и еду, которую она будет собирать. В коде эти объекты будут представлены в виде простых структур данных, хранящих их координаты и состояние.
Этот код следует добавить в ваш файл snake_game.py
сразу после блока с константами и созданием окна.
Шаг 1: Представление змейки
Змейка — это не единый объект, а последовательность связанных сегментов. Наиболее удобный способ представить ее в коде — использовать список, где каждый элемент является списком из двух чисел: [x, y]
координат сегмента.
Мы определим три переменные для управления змейкой:
snake_pos
: Текущие координаты головы змейки. Мы будем обновлять именно их для симуляции движения.snake_body
: Список всех сегментов змейки. Первый элемент (snake_body[0]
) всегда будет совпадать сsnake_pos
.snake_direction
: Строковая переменная для хранения текущего направления движения.
# Начальное положение змейки
# Голова змейки появляется в определенной позиции
snake_pos = [100, 60]
# Тело змейки: список списков с координатами каждого сегмента
# Изначально состоит из 3-х сегментов
snake_body = [[100, 60], [80, 60], [60, 60]]
# Начальное направление движения змейки
snake_direction = 'RIGHT'
Мы разместили змейку горизонтально, с головой в точке (100, 60)
. Обратите внимание, что все координаты кратны BLOCK_SIZE
(20), что обеспечивает выравнивание объектов по сетке.
Шаг 2: Создание еды
Еда — это одиночный блок, который должен появляться в случайном месте на игровом поле. Как и сегменты змейки, еда будет представлена списком с [x, y]
координатами.
Для генерации случайных координат нам понадобится модуль random
, который мы импортировали в самом начале.
# Позиция еды
# Генерируется случайным образом в пределах игрового поля
food_pos = [random.randrange(0, SCREEN_WIDTH // BLOCK_SIZE) * BLOCK_SIZE,
random.randrange(0, SCREEN_HEIGHT // BLOCK_SIZE) * BLOCK_SIZE]
Давайте разберем эту строку:
SCREEN_WIDTH // BLOCK_SIZE
: Эта операция вычисляет, сколько блоков помещается по ширине экрана. Например,640 // 20 = 32
.random.randrange(0, ...)
: Генерирует случайное целое число — номер ячейки в нашей сетке (например, от 0 до 31).... * BLOCK_SIZE
: Умножает случайный номер ячейки на размер блока, чтобы преобразовать его обратно в пиксельные координаты. Например, если сгенерировалось число 5, координата будет5 * 20 = 100
.
Этот подход гарантирует, что еда всегда будет появляться ровно в ячейке сетки, а не между ними, что критически важно для механики игры.
На данном этапе мы определили начальное состояние нашей игры. У нас есть все данные, чтобы отрисовать первый кадр: змейку и еду в их стартовых позициях. В следующем разделе мы создадим главный игровой цикл, который заставит эти объекты двигаться и взаимодействовать.
Раздел 3: Сердце игры — главный игровой цикл
Сердце любой игры — это главный игровой цикл (Game Loop). Это непрерывно выполняющийся блок кода, который отвечает за три ключевые задачи на каждом кадре:
Обработка ввода (Input): Проверка действий игрока (нажатия клавиш, движения мыши).
Обновление состояния (Update): Изменение позиций и состояний игровых объектов на основе логики и ввода.
Отрисовка (Render): Перерисовка экрана для отображения нового состояния игры.
Весь последующий код необходимо поместить в конец файла snake_game.py
.
Основная структура игрового цикла
Мы создадим флаг game_over
, который будет управлять работой цикла. Пока он имеет значение False
, игра продолжается.
game_over = False
while not game_over:
# 1. Обработка событий
for event in pygame.event.get():
if event.type == pygame.QUIT:
game_over = True
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_UP and snake_direction != 'DOWN':
snake_direction = 'UP'
elif event.key == pygame.K_DOWN and snake_direction != 'UP':
snake_direction = 'DOWN'
elif event.key == pygame.K_LEFT and snake_direction != 'RIGHT':
snake_direction = 'LEFT'
elif event.key == pygame.K_RIGHT and snake_direction != 'LEFT':
snake_direction = 'RIGHT'
# 2. Обновление состояния (пока здесь будет только отрисовка)
# Логику движения и столкновений добавим в следующем разделе.
# 3. Отрисовка
# Заливаем фон черным цветом
screen.fill(BLACK)
# Рисуем змейку
for pos in snake_body:
pygame.draw.rect(screen, GREEN, pygame.Rect(pos[0], pos[1], BLOCK_SIZE, BLOCK_SIZE))
# Рисуем еду
pygame.draw.rect(screen, RED, pygame.Rect(food_pos[0], food_pos[1], BLOCK_SIZE, BLOCK_SIZE))
# Обновляем экран, чтобы отобразить нарисованное
pygame.display.flip()
# Устанавливаем FPS (кадры в секунду)
clock.tick(15)
# Корректное завершение работы
pygame.quit()
quit()
Подробный разбор цикла
A. Обработка событий
pygame.event.get()
: Эта функция получает список всех событий, произошедших с момента ее последнего вызова (нажатия клавиш, клики мыши и т.д.). Мы перебираем этот список в цикле.if event.type == pygame.QUIT
: Это событие возникает, когда пользователь нажимает на крестик для закрытия окна. Мы устанавливаемgame_over = True
, чтобы выйти из главного цикла.-
if event.type == pygame.KEYDOWN
: Событие означает, что была нажата какая-то клавиша.event.key
: Атрибут, который содержит код нажатой клавиши (например,pygame.K_UP
для стрелки вверх).Мы проверяем, какая стрелка была нажата, и обновляем переменную
snake_direction
.Важное условие
and snake_direction != '...'
не позволяет змейке мгновенно развернуться на 180 градусов (например, с 'UP' на 'DOWN'), что в классической «Змейке» привело бы к мгновенному столкновению с собственным телом.
B. Отрисовка (Рендеринг)
Процесс отрисовки кадра всегда следует одному и тому же порядку:
screen.fill(BLACK)
: Сначала мы полностью заливаем экран одним цветом (в нашем случае черным). Это необходимо, чтобы стереть изображение с предыдущего кадра.pygame.draw.rect()
: Мы используем эту функцию для рисования прямоугольников. Мы проходим в цикле по всем сегментамsnake_body
и рисуем зеленый квадрат для каждого. Затем рисуем один красный квадрат для еды.pygame.display.flip()
: Эта команда делает видимым все, что мы нарисовали на экране. Без нее пользователь ничего бы не увидел.
C. Контроль частоты кадров (FPS)
clock.tick(15)
: Здесь мы используем созданный ранее объектclock
. Эта строка говорит Pygame, что наш цикл должен выполняться не чаще 15 раз в секунду. Это задает скорость игры и обеспечивает ее стабильность на компьютерах разной производительности.
D. Завершение работы
Когда цикл while
завершается (когда game_over
становится True
), выполняются две последние команды:
pygame.quit()
: Деинициализирует все модули Pygame. Это корректный способ завершить работу с библиотекой.quit()
: Завершает выполнение Python-скрипта.
Если вы запустите код сейчас, вы увидите статичную картинку со змейкой и едой. Окно будет реагировать на нажатие стрелок (внутри переменная snake_direction
будет меняться) и на закрытие. В следующем разделе мы добавим логику движения, чтобы превратить эту статичную сцену в настоящую игру.
Раздел 4: Игровая логика: движение, рост и столкновения
На данный момент у нас есть окно, объекты и обработка ввода. Теперь нам нужно связать их воедино: заставить змейку двигаться в соответствии с нажатиями клавиш, расти при поедании еды и реагировать на столкновения. Вся эта логика будет добавлена в секцию «Обновление состояния» нашего главного игрового цикла.
Шаг 1: Реализация движения
Движение змейки — это простое изменение координат ее головы (snake_pos
) в каждом кадре в зависимости от текущего направления (snake_direction
). Этот код нужно вставить в игровой цикл сразу после блока обработки событий (for event in...
).
# ... после блока for event in pygame.event.get(): ...
# 2. Обновление состояния
# Обновляем координаты головы змейки
if snake_direction == 'UP':
snake_pos[1] -= BLOCK_SIZE
elif snake_direction == 'DOWN':
snake_pos[1] += BLOCK_SIZE
elif snake_direction == 'LEFT':
snake_pos[0] -= BLOCK_SIZE
elif snake_direction == 'RIGHT':
snake_pos[0] += BLOCK_SIZE
Шаг 2: Механика роста и обновления тела
Просто изменить координаты головы недостаточно — все тело должно следовать за ней. Для этого используется элегантный алгоритм:
В начало списка
snake_body
мы добавляем новый сегмент с обновленными координатами головы.Если змейка не съела еду, мы удаляем самый последний сегмент из списка
snake_body
.
В результате общая длина змейки остается прежней, но создается полная иллюзия движения всего тела. Если же змейка съела еду, мы просто пропускаем шаг удаления хвоста, и ее длина увеличивается на один сегмент.
Этот код добавляется сразу после блока обновления координат:
# ... после обновления snake_pos ...
# Добавляем новую голову в начало тела змейки
snake_body.insert(0, list(snake_pos))
# Проверяем, съела ли змейка еду
if snake_pos[0] == food_pos[0] and snake_pos[1] == food_pos[1]:
# Еда съедена: не удаляем хвост, генерируем новую еду
food_spawned = False
while not food_spawned:
# Генерируем новую позицию
new_food_pos = [random.randrange(0, SCREEN_WIDTH // BLOCK_SIZE) * BLOCK_SIZE,
random.randrange(0, SCREEN_HEIGHT // BLOCK_SIZE) * BLOCK_SIZE]
# Проверяем, что новая еда не появляется на теле змейки
if new_food_pos not in snake_body:
food_pos = new_food_pos
food_spawned = True
else:
# Еда не съедена: удаляем последний сегмент (хвост)
snake_body.pop()
Важный момент: мы добавили проверку, чтобы новая еда не сгенерировалась случайно "внутри" уже существующей змейки.
Шаг 3: Логика проигрыша (обработка столкновений)
Игра должна заканчиваться в двух случаях: при столкновении со стеной или с собственным хвостом. Эти проверки нужно добавить сразу после логики движения и роста.
# ... после логики роста ...
# Проверка на столкновение со стенами
if snake_pos[0] < 0 or snake_pos[0] >= SCREEN_WIDTH:
game_over = True
if snake_pos[1] < 0 or snake_pos[1] >= SCREEN_HEIGHT:
game_over = True
# Проверка на столкновение с собственным телом
# Проверяем, совпадает ли голова с какой-либо частью тела (кроме самой головы)
for block in snake_body[1:]:
if snake_pos[0] == block[0] and snake_pos[1] == block[1]:
game_over = True
break # Выходим из цикла, если столкновение найдено
Столкновение со стенами: Мы просто проверяем, не вышли ли координаты головы
snake_pos
за пределы игрового окна.Столкновение с собой: Мы перебираем все сегменты тела змейки, начиная со второго (
snake_body[1:]
), и сравниваем их координаты с координатами головы. Если находится совпадение — игра окончена.
Теперь у вас есть полностью играбельная версия «Змейки»! Она движется, растет и корректно обрабатывает условия проигрыша. В последнем разделе мы добавим финальный штрих — отображение счета, чтобы сделать игру завершенной.
Раздел 5: Доработка: счет и вывод текста
Наша игра уже функциональна, но ей не хватает важного элемента — обратной связи для игрока. Отображение счета не только мотивирует играть дальше, но и является отличным поводом изучить, как выводить текст на экран с помощью Pygame.
Шаг 1: Инициализация шрифта и переменной для счета
Для начала нам нужно определить, какой шрифт мы будем использовать. Это делается один раз, в самом начале программы, вместе с другими константами.
Добавьте этот код в секцию с константами в snake_game.py
:
# ... после определения цветов ...
# Шрифты
FONT_STYLE = pygame.font.SysFont('bahnschrift', 25) # Вы можете выбрать другой шрифт
pygame.font.SysFont()
создает объект шрифта. Первым аргументом идет название шрифта (выберите любой, установленный в вашей системе, или None
для шрифта по умолчанию), вторым — его размер.
Теперь, перед началом главного игрового цикла, инициализируем переменную для хранения счета:
# ... перед `game_over = False` ...
score = 0
Шаг 2: Создание функции для отображения счета
Чтобы не загромождать главный цикл, создадим отдельную функцию для рендеринга и отображения текста. Процесс вывода текста в Pygame состоит из двух шагов: сначала создается поверхность с текстом (render
), а затем эта поверхность отрисовывается на основном экране (blit
).
Добавьте эту функцию в начало файла, после инициализации шрифта:
def show_score(current_score):
# Создаем поверхность с текстом
score_text = FONT_STYLE.render("Счет: " + str(current_score), True, WHITE)
# Отрисовываем текст в левом верхнем углу
screen.blit(score_text, [10, 10])
FONT_STYLE.render()
: Метод принимает текст, флаг сглаживания (True
для более качественного отображения) и цвет текста. Он возвращает новую поверхность (Surface
), на которой "нарисован" указанный текст.screen.blit()
: Этот метод "копирует" пиксели с одной поверхности на другую. Здесь мы копируем текст с поверхностиscore_text
на главный экранscreen
по координатам[10, 10]
.
Шаг 3: Обновление и отображение счета в игре
Осталось сделать две вещи:
Увеличивать счет каждый раз, когда змейка съедает еду.
Вызывать нашу функцию
show_score()
в каждом кадре, чтобы счет всегда был виден.
Найдите в игровом цикле блок, где проверяется столкновение змейки с едой, и добавьте туда score += 1
:
# ... в игровом цикле ...
# Проверяем, съела ли змейка еду
if snake_pos[0] == food_pos[0] and snake_pos[1] == food_pos[1]:
score += 1 # Увеличиваем счет
# ... остальная логика поедания еды ...
А теперь вызовем функцию отображения. Это нужно делать в секции отрисовки, после заливки фона, но перед pygame.display.flip()
:
# ... в секции "Отрисовка" игрового цикла ...
screen.fill(BLACK)
# Рисуем змейку, еду...
# ...
# Отображаем текущий счет
show_score(score)
# Обновляем экран
pygame.display.flip()
Теперь, когда вы запустите игру, в левом верхнем углу будет отображаться ваш текущий счет, который увеличивается с каждым съеденным блоком.
На этом разработка основной механики «Змейки» завершена. Вы прошли путь от пустого файла до полноценной интерактивной игры, изучив ключевые принципы работы с Pygame.
Заключение и дальнейшие шаги
Поздравляем! Вы успешно прошли полный цикл разработки, превратив пустой файл в законченную и функционирующую игру «Змейка».
Код проекта оставил на github тут.
Анонс новых статей, полезные материалы, а так же если в процессе написания кода возникнут сложности, обсудить их или задать вопрос по статье можно в моём Telegram-сообществе.
Теперь у вас есть прочная база для создания собственных, более сложных проектов с помощью Pygame.
Комментарии (6)
RalphMirebs
01.10.2025 14:24А в чём плюс того, что элемент змейки хранит положение на сетке?
Что если хранить в элементах не координаты, а направление в котором можно найти следующий сегмент? Например, в виде чисел 0,1,2,3,4 (1 - вверх, 2 вправо... 0 - хвост)
Так на длинной змее мы сэкономим заметно памяти...enamored_poc Автор
01.10.2025 14:24Ну конечно слово заметно это вопрос спорный, так вряд ли у нас змейка будет 1000 + длинной ( что на самом деле тоже не так больно). Можно оптимизировать, но придется наверное писать дополнительно код и усложнять не много логику, как мне кажется это можно не делать.
Desaider
01.10.2025 14:24Думаю как в игровой форме приобщить ребёнка к программированию, сталкиваюсь с тем что библиотеки для игр слишком громоздкие. И пока объясняешь начальные настройки интерес к программированию угасает.
Ikobolok
Круто. Наверно чем то полезная была статья когда то. Но интересней другое. У вас там живая очередь постить сюда свой мусор ради ссылки на канал, или вы как то договариваетесь и по очереди ежедневно это делаете?
enamored_poc Автор
Статьи ориентированы на начинающих. Если вы все знаете и вам это не интересно можете не читать мою статью. Ссылка на канал нужна в случае помощи или обсудить тему из статьи, потому что в хабре я не всегда могу увидеть чей-то комментарий.
Ikobolok
Начинающих осваивать копипаст. Скопировал вставил.
P.S. С удовольствием бы не видел. Но вы бизнесмены, простите, засрали всю ленту, своим мусором, ради ссылки на телеграмм.