Найти в Дзене
ZDG

Пишем Питона на Питоне #17: Игровые уровни

В этой части делаем несколько уровней, переходы между ними и конец игры.

Предыдущие части: Строим глазки, Таблица рекордов, Шрифт, Остановите музыку, Включите звук, Жизни и модальные окна, Подсчёт очков, Главное меню, Геймплей, Есть ли у него душа, Организация ввода, View, Визуализация поля, Загрузка уровня, INI-файл, Пишем Питона на Питоне!

Код для этой части находится в ветке levels на github. Вы можете смотреть там все файлы онлайн и также скачать зип-архив всей ветки.

Довольно неожиданно, что такая простая игра, как "змейка", требует столько работы, не правда ли? Но финал близок. Осталось сделать переходы с уровня на уровень и затем всё отполировать.

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

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

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

После появления выхода игрок может продолжать играть на уровне, но как только голова питона попадёт в клетку с выходом, уровень закончится.

Чтобы сделать список из нескольких уровней, подготовим каждый отдельный уровень в виде текстовых файлов level01.txt, level02.txt, level03.txt.

Затем в дело наконец-то идёт INI-файл. В нём мы перечислим идентификаторы уровней в нужном порядке. Идентификаторы не обязательно должны совпадать с именами файлов, они нужны только для организации INI-файла:

levels = level01, level02, level03

Мы перечислили три (пока что) идентификатора, и теперь для каждого из них создадим одноимённую секцию, вот например она для первого уровня:

[level01]
apple_count = 10
file = level01.txt

В секции будут перечислены:

  • количество яблок, необходимое для прохождения уровня
  • имя файла с картой уровня

Все эти записи после чтения INI-файла автоматически появятся как свойства объекта config. Зная список идентификаторов уровней, мы можем по идентификатору обращаться к любой из секций.

Доработаем компонент Game. Передадим ему в конструктор объект config, из которого он достанет список уровней. С этим списком нужно провести кое-какую подготовку: из конфига он приходит просто как строка "level01, level02, level03", поэтому строку надо разбить на части, используя знак "," как разделитель, и каждую полученную часть почистить от лишних пробелов.

Всё это я оформил в метод Game.load_level_list(). Второй метод – Game.load_level() – уже загружает уровень с заданным номером.

Теперь нужно доработать буквально всё:

  1. Представление данных в карте уровня
  2. Отрисовка выхода в представлении GameView
  3. Дополнительная логика обработки выхода в контроллере GameController
  4. Дополнительные состояния питона Python.EXIT и Python.OUT
  5. Дополнительные состояния игры: Game.MODE_START_LEVEL для перехода между уровнями и Game.MODE_FINISH для конца.
  6. Дополнительные MVC-классы: контроллеры, модели меню и представления для перехода между уровнями и для окончательного финала игры.

Офигеть сколько. Но это больше механическая работа, так что бояться не нужно. Итак, по очереди:

  1. В текстовом файле уровня выход обозначается английской буквой "E". При загрузке она транслируется в константу Level.EXIT. Выходов может быть сколько угодно.
  2. Отрисовку выхода я сделал так же, как яблоко – пульсирующим квадратом, но цвета стены. Так что метод draw_apple() я переделал в более общий draw_pulse_box() с передачей туда нужного цвета. Кроме того, в верхнюю статусную строку добавился вывод количества яблок. Шрифт очень большой (уже много раз пожалел), поэтому, чтобы всё поместилось и чтобы не делать новый шрифт, мне пришлось вернуться к надписям в виде картинок.
  3. В GameController добавлено условие на столкновение головы с выходом. Оно, конечно, срабатывает только тогда, когда собрано нужное количество яблок. На выходе питон укорачивается так же, как при смерти, но у него устанавливается другой режим: Python.EXIT.
  4. В классе Python добавлена обработка режима EXIT, который переходит в режим OUT (т.е. питон весь вышел с уровня). Когда контроллер видит у питона режим OUT, это значит, что нужно переводить игру в режим Game.MODE_START_LEVEL или Game.MODE_FINISH.
  5. В классе Game написана логика для дополнительных состояний. Она состоит лишь в том, чтобы породить нужные представления.
  6. Представления и контроллеры для вышеописанных состояний ничем особенным не отличаются от других, так что не буду их описывать. Для режима MODE_FINISH вообще используется уже готовый контроллер GameOverController, который также в случае необходимости направит игрока на ввод имени в таблицу рекордов.
представление для MODE_START_LEVEL
представление для MODE_START_LEVEL
представление для MODE_FINISH
представление для MODE_FINISH

И наконец всё работает. Наступает время дизайна уровней и утомительного тестирования. Используя INI-файл, можно менять количество и порядок уровней, назначать разное количество яблок на уровень и т.д., чтобы облегчить задачу.

В следующий (вероятно, последний) раз займёмся пунктом меню "Настройки" и общими доработками по части геймплея.

Следующая часть: Настройки игры

Читайте также: