Создание классических аркадных игр — один из лучших способов применить теоретические знания 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()

Давайте разберем, что делает этот код:

  1. pygame.init(): Запускает все необходимые модули Pygame. Эту функцию нужно вызывать в самом начале.

  2. Константы: Мы выносим в переменные с заглавными буквами значения, которые не будут меняться в ходе игры. Это улучшает читаемость кода и упрощает его изменение в будущем. Например, если вы захотите увеличить игровое поле, достаточно будет поменять значения SCREEN_WIDTH и SCREEN_HEIGHT в одном месте.

  3. pygame.display.set_mode(): Эта функция создает главное окно (поверхность), на которой будет происходить отрисовка. Мы передаем ей кортеж с шириной и высотой.

  4. pygame.time.Clock(): Создает специальный объект, который поможет нам управлять скоростью игры, ограничивая количество кадров в секунду (FPS). Это гарантирует, что игра будет работать с одинаковой скоростью на компьютерах разной мощности.

На этом этапе у нас есть готовая основа для проекта: настроенное окружение и код, который создает пустое черное окно с заголовком "Змейка на Pygame".

Раздел 2: Создание игровых объектов (Змейка и Еда)

Теперь, когда у нас есть «сцена» в виде игрового окна, пора добавить на нее «актеров»: змейку, которой будет управлять игрок, и еду, которую она будет собирать. В коде эти объекты будут представлены в виде простых структур данных, хранящих их координаты и состояние.

Этот код следует добавить в ваш файл snake_game.py сразу после блока с константами и созданием окна.

Шаг 1: Представление змейки

Змейка — это не единый объект, а последовательность связанных сегментов. Наиболее удобный способ представить ее в коде — использовать список, где каждый элемент является списком из двух чисел: [x, y] координат сегмента.

Мы определим три переменные для управления змейкой:

  1. snake_pos: Текущие координаты головы змейки. Мы будем обновлять именно их для симуляции движения.

  2. snake_body: Список всех сегментов змейки. Первый элемент (snake_body[0]) всегда будет совпадать с snake_pos.

  3. 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). Это непрерывно выполняющийся блок кода, который отвечает за три ключевые задачи на каждом кадре:

  1. Обработка ввода (Input): Проверка действий игрока (нажатия клавиш, движения мыши).

  2. Обновление состояния (Update): Изменение позиций и состояний игровых объектов на основе логики и ввода.

  3. Отрисовка (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. Отрисовка (Рендеринг)

Процесс отрисовки кадра всегда следует одному и тому же порядку:

  1. screen.fill(BLACK): Сначала мы полностью заливаем экран одним цветом (в нашем случае черным). Это необходимо, чтобы стереть изображение с предыдущего кадра.

  2. pygame.draw.rect(): Мы используем эту функцию для рисования прямоугольников. Мы проходим в цикле по всем сегментам snake_body и рисуем зеленый квадрат для каждого. Затем рисуем один красный квадрат для еды.

  3. 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: Механика роста и обновления тела

Просто изменить координаты головы недостаточно — все тело должно следовать за ней. Для этого используется элегантный алгоритм:

  1. В начало списка snake_body мы добавляем новый сегмент с обновленными координатами головы.

  2. Если змейка не съела еду, мы удаляем самый последний сегмент из списка 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: Обновление и отображение счета в игре

Осталось сделать две вещи:

  1. Увеличивать счет каждый раз, когда змейка съедает еду.

  2. Вызывать нашу функцию 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)


  1. Ikobolok
    01.10.2025 14:24

    Круто. Наверно чем то полезная была статья когда то. Но интересней другое. У вас там живая очередь постить сюда свой мусор ради ссылки на канал, или вы как то договариваетесь и по очереди ежедневно это делаете?


    1. enamored_poc Автор
      01.10.2025 14:24

      Статьи ориентированы на начинающих. Если вы все знаете и вам это не интересно можете не читать мою статью. Ссылка на канал нужна в случае помощи или обсудить тему из статьи, потому что в хабре я не всегда могу увидеть чей-то комментарий.


      1. Ikobolok
        01.10.2025 14:24

        Начинающих осваивать копипаст. Скопировал вставил.
        P.S. С удовольствием бы не видел. Но вы бизнесмены, простите, засрали всю ленту, своим мусором, ради ссылки на телеграмм.


  1. RalphMirebs
    01.10.2025 14:24

    А в чём плюс того, что элемент змейки хранит положение на сетке?
    Что если хранить в элементах не координаты, а направление в котором можно найти следующий сегмент? Например, в виде чисел 0,1,2,3,4 (1 - вверх, 2 вправо... 0 - хвост)
    Так на длинной змее мы сэкономим заметно памяти...


    1. enamored_poc Автор
      01.10.2025 14:24

      Ну конечно слово заметно это вопрос спорный, так вряд ли у нас змейка будет 1000 + длинной ( что на самом деле тоже не так больно). Можно оптимизировать, но придется наверное писать дополнительно код и усложнять не много логику, как мне кажется это можно не делать.


  1. Desaider
    01.10.2025 14:24

    Думаю как в игровой форме приобщить ребёнка к программированию, сталкиваюсь с тем что библиотеки для игр слишком громоздкие. И пока объясняешь начальные настройки интерес к программированию угасает.