Пишем игру Pac-Man на Python в 300 строк кода

Пишем игру Pac-Man на Python в 300 строк кода


(Данное изображение и все последующие взяты отсюда.)

Монстры представлены в виде четырех призраков, которые атакуют игрока последовательными «волнами», подобно космическим захватчикам. Каждый призрак также имеет уникальную личность. В истории есть еще один важный элемент, концепция жизненной силы «кокоро», которая позволяла существам поедать монстров. В игре эта энергия представлена ​​​​в виде печенья с усилением, которое дает Pac-Man кратковременную способность поедать монстров.



В этом уроке я сначала проведу вас через базовую настройку, затем мы создадим игровые объекты для стены лабиринта, Pac-Man и призраков, обеспечим поиск пути в лабиринте, зададим призракам случайное движение, реализуем элементы управления стрелками для игрока и наконец, разместим еду в виде печенья по всему лабиринту.


>> basic_settings

Получившаяся игра содержит примерно 300 строк кода, поэтому я перечисляю здесь только самые важные части. Полный код доступен в моем репозитории GitHub. Первым делом установим необходимые пакеты. Нам понадобятся pygame, numpy и tcod. Установите их все с помощью инструмента pip. Если вы используете IDE, такую ​​как PyCharm (я рекомендую ее), установка произойдет после нажатия на сообщение об ошибке отсутствующего пакета.

Во-первых, мы создадим игровое окно, аналогично предыдущему руководству по игре Space Invaders (в котором было всего 100 строк). Здесь я подготовлю параметры для указания размера окна, названия игры, частоты обновления и несколько полей данных, которые будут содержать ссылки на игровые объекты и игрока. Функция tick итеративно проходит по всем игровым объектам и вызывает их внутреннюю логику и рендеринг. Затем остается только перерисовать всю игровую область и обработать события ввода, такие как щелчки мышью и ввод с клавиатуры. Для этой цели будет служить функция _handle_events.

Интересно, перейти к каналу>> parent_game_object

Затем я создаю родительский игровой объект с именем GameObject, от которого другие классы будут наследовать функциональность. В игре у нас будут объекты для стены (Wall), Pac-Man (Hero), привидения (Ghost) и печенья (Cookie). Для упомянутых выше подвижных игровых объектов позже я создам класс MovableObject, который будет расширением класса GameObject с функциями перемещения.

Во время инициализации объекта я задаю его цвет, форму и положение. Каждый объект также имеет ссылку на поверхность рендеринга _surface, так что он может сам заботиться о своем рендеринге на основной поверхности. Для этой цели у нас есть функция draw, которая вызывается ранее созданным GameRenderer для каждого игрового объекта. В зависимости от параметра is_circle объект отображается либо в виде круга, либо в виде прямоугольника (в нашем случае я использую квадрат со слегка закругленными углами для стен и круг для Pac-Man и печенья).

Создать класс стены будет просто. Для стен выбираю синий цвет согласно оригинальному Pac-Man (параметр цвета — Blue 255, остальное 0).

Подготовлен код для рендеринга и объект для стен. При написании следите за тем, чтобы классы Wall и GameObject были выше класса GameRenderer, чтобы класс их «видел». Следующим шагом является визуализация лабиринта на экране. Но перед этим мы должны создать один вспомогательный класс.

>> the_game_controller_class

Я сохраню лабиринт в символах ASCII в переменной в новом классе PacmanGameController. Я буду использовать исходный размер лабиринта — 28x31 тайл. Позже мне нужно будет убедиться, что призраки смогут правильно пройти через лабиринт и, возможно, найти игрока. Сначала я прочитаю лабиринт как символы и преобразую его в матрицу единиц и нулей, где стена равна нулю, а проходимое пространство равно единице. Эти значения служат алгоритму поиска пути в качестве так называемой функции стоимости. Ноль означает бесконечную стоимость прохождения, поэтому элементы в массиве, отмеченные таким образом, не будут считаться проходимыми. Обратите внимание на массив reachable_spaces, который содержит проходимые части лабиринта. Но об этом позже, сначала я должен подготовить структуры классов. Вы можете скопировать лабиринт в формате ASCII с моего GitHub. В обозначении персонажа я использовал X для стены, P для Pac-Man и G для призрака.

>> rendering_the_maze

Все необходимое для рендеринга лабиринта подготовлено, поэтому осталось только создать экземпляры наших классов PacmanGameController, пройтись по 2D-массиву с позициями стен и создать в этих местах объект Wall (я использую функцию add_wall, которая не показана здесь, еще раз взгляните на полный код на моем GitHub). Я установил частоту обновления 120 кадров в секунду.

>> let’s_add_ghosts!

В оригинальном Pac-Man было четыре призрака по имени Блинки, Пинки, Инки и Клайд, каждый со своим характером и способностями. Концепция игры основана на японской сказке (подробнее здесь и здесь), а оригинальные названия на японском языке также предполагают их способности (например, у Пинки японское имя Вор, у Блинки — Тень). Однако для нашей игры мы не будем вдаваться в такие подробности, и каждый призрак будет использовать только базовый поведенческий цикл, как и в оригинале, то есть режимы «преследование», «рассеивание» и «испуг». Мы опишем и обработаем эти режимы ИИ во второй части.

Класс-призрак будет простым, унаследовав большую часть своего поведения от родительского класса MovableObject (посмотрите мой GitHub, этот класс немного сложнее и включает логику для движения в четырех направлениях, следования по маршруту и ​​проверки на столкновения со стенами).

Я добавлю значения RGB для цветов каждого призрака в класс PacmanGameController и сгенерирую четыре цветных призрака в основной функции. Я также подготовлю статическую функцию для преобразования координат, которая просто преобразует координаты лабиринта (например, x=16 y=16 приблизительно соответствует центру лабиринта, и умножение на размер ячейки или тайла дает мне координату на поверхность игры в пикселях).

На этом этапе четыре призрака будут отображаться в лабиринте при запуске игры. Теперь мы хотим заставить их двигаться.

>> maze_pathfinding

Теперь наступает, пожалуй, самая сложная часть. Поиск пути в двумерном пространстве или графе — сложная задача. Реализация алгоритма решения такой задачи заняла бы отдельную статью, поэтому воспользуемся готовым решением. Наиболее эффективным алгоритмом поиска пути является алгоритм A*. Это обеспечивается пакетом tcod, который мы установили вначале.

Чтобы перемещать призраков, я создам класс с именем Pathfinder. В конструкторе я инициализирую массив numpy со стоимостью прохождения (массив единиц и нулей, описанный ранее) и создам переменную класса pf, которая будет содержать экземпляр навигатора A*. Затем функция get_path рассчитает и вернет путь в виде серии шагов в массиве при вызове с координатами в лабиринте (откуда, куда).

Теперь я добавлю раздел к основной функции, чтобы продемонстрировать поиск пути. Я выбираю начальные координаты [1,1] и пункт назначения маршрута [24,24]. Это необязательный код.

В игре рендеринг кратчайшего маршрута выглядит так:

>> randomized_ghost_movement

Теперь мы собираемся создать новую функцию в классе PacmanGameController для выбора случайной точки из массива reachable_spaces. Каждый призрак будет использовать эту функцию после того, как достигнет места назначения. Таким образом, призраки будут просто выбирать путь от своего текущего положения в лабиринте до случайного места назначения на неопределенный срок. В следующей части мы реализуем более сложное поведение, такое как погоня и бегство от игрока.

В классе Ghost мы добавляем новую логику для следования по пути. Функция reached_target вызывает каждый кадр и проверяет, достиг ли уже призрак своей цели. Если да, то он определяет, в каком направлении находится следующий шаг пути через лабиринт, и начинает менять свое положение вверх, вниз, влево или вправо (логика движения вызывается в родительском классе MovableObject).

Призраки теперь создаются в позициях, обозначенных буквой G в исходном лабиринте ASCII, и начинают искать случайный путь. Я запер в клетке трех призраков — как и в оригинальном Pacman, они будут выпускаться по одному — и один бродит по лабиринту:

>> player_controls

Чтобы добавить функциональность плеера, я создам класс под названием Hero. Большая часть логики управления как игроком, так и призраками обрабатывается в классе MovableObject, поэтому нам нужно всего несколько функций для определения поведения игрока. В оригинальном Pacman игрок может двигаться в четырех направлениях, управляемых клавишами со стрелками. Если ни одна клавиша со стрелкой не нажата, игрок продолжит движение в последнем допустимом направлении. Если клавиша нажата в направлении, в котором игрок не может двигаться, это направление сохраняется и используется в следующем доступном ходе. Я воспроизведу это поведение в нашей игре, а также добавлю способность Пакмана телепортироваться из одного конца лабиринта в другой — я просто проверю, находится ли игрок вне игровой зоны с левой или правой стороны, и установлю его положение на противоположную сторону лабиринта соответственно. В Pacman также есть модифицированная функция рендеринга, нам нужно отображать его вдвое меньше, чем он обычно занимает (используя pygame.rect).

Я создаю экземпляр класса Hero в конце основной функции. Задаю позицию с координатами [1,1] — unified_size — размер одного тайла. Также нам нужно добавить обработку входных событий в класс GameRenderer, чтобы мы могли управлять игровым персонажем.

После запуска игры теперь мы можем управлять игроком — Pacman!

>> adding_cookies

Это был бы не Пакман без печенья в лабиринте. С точки зрения игрового процесса они определяют степень исследования мира, а некоторые файлы cookie даже меняют способности призраков и Пакмана. Таким образом, они являются высшей наградой для игроков и основным показателем их прогресса в уровнях. В современных играх поведение, которое геймдизайнер хочет поощрять в игроке, обычно вознаграждается. Прекрасным примером является Elden Ring, где каждый, кто исследует каждый уголок мира, получает вознаграждение. Чем опаснее и отдаленнее, тем больше награда. С другой стороны, такие игры, как Assassin's Creed, поддерживают выполнение задач, поэтому во время игры у вас возникает ощущение, что вы работаете, а не играете.

Добавление файлов cookie будет самым простым во всем уроке, поэтому я оставил его на конец, как вишенку на торте. Я создам класс под названием Cookie. Его экземпляр всегда будет иметь размер четыре пикселя, желтый цвет и круглую форму. В основной функции я создам куки на всех тайлах, которые мы вначале сохранили в массиве cookie_spaces (тот же, что и reachable_spaces). Я добавлю в плеер функцию с именем handle_cookie_pickup, в которой я постоянно проверяю, не сталкивается ли плеер с каким-либо cookie. Если это так, я удалю файл cookie из массива, и он больше не будет отображаться.

А теперь о результате наших стараний:

Небольшой интересный факт напоследок — в оригинальной игре Пакман останавливается на один кадр после того, как съест каждое печенье, поэтому призракам легче его поймать в начале игры, когда поле еще заполнено. В следующей части мы реализуем аналогичную игровую механику, и вы также можете рассчитывать на искусственный интеллект для призраков, отслеживание очков, звуки, анимацию, текстуры, бонусы, эффекты дрожания экрана, жизни и конечные состояния игры.

Источники



Report Page