Пишем свою игру в жанре Roguelike

Рассказывает Ido Yehieli 


Игры в жанре roguelike, такие как Dungeons of Dredmor, Spelunky, The Binding of Isaac и FTL, в последнее время стали очень популярны, а различные комбинации элементов этого жанра теперь добавляют многим играм глубины и реиграбельности.

3D-рогалик Warfarer

Следуя инструкциям этого руководства, вы создадите традиционный «рогалик», используя игровой движок Phaser на JS+HTML5. Кстати, недавно мы публиковали обзор таких движков. В результате вы получите полнофункциональную игру в жанре «roguelike», запускаемую в браузере. (Под рогаликом мы подразумеваем одиночный рандомизированный пошаговый dungeon-crawler с одной жизнью.)

Click to play the game

Нажмите, чтобы сыграть.

Примечание: хотя в этом руководстве и используются JavaScript, HTML и Phaser, вы можете использовать эти принципы для реализации на любом другом языке и движке.

Подготовка

Вам понадобятся текстовый редактор и браузер. Я использую Notepad++ и Google Chrome, но это не принципиально.

Затем вы должны скачать исходники и начать с папки init: она содержит файлы Phaser, HTML и JS, необходимые для нашей игры. Наш код мы будем писать в пустом файле rl.js.

Файл index.html file просто загружает Phaser и вышеупомянутый файл с кодом игры:

Инициализация и определения

Сейчас для нашей игры мы будем использовать символы ASCII — впоследствии их можно заменить bitmap-графикой, но сейчас проще взять ASCII.

Давайте зададим несколько констант для размера шрифта, размера карты и количества персонажей:

Также инициализируем Phaser и слушатели сигналов с клавиатуры, так как мы создаём пошаговую игру и хотим создавать действие после каждого нажатия клавиши:

Так как ширина стандартных моноширинных шрифтов равна 60% от высоты, мы зададим размер поля как 0.6 * размер шрифта * количество столбцов. Мы также говорим Phaser, что он должен вызвать нашу функцию create() сразу после завершения инициализации, когда инициализируется и управление с клавиатуры.

Можете взглянуть на нашу игру — правда, там пока и смотреть не на что:)

Карта

Клеточная карта отражает нашу игровую зону: дискретный двумерный массив клеток, представленных символами ASCII, которые могут изображать либо стену (#: блокирует перемещение), либо пол (.: не блокирует перемещение):

Давайте будем использовать простейшую форму процедурной генерации карт: каждая клетка принимает одно из двух значений случайным образом:

Таким образом мы получим карту, примерно на 20% занятую стенами.

Мы инициализируем новую карту в функции create() сразу после запуска слушателей клавиатуры:

Можете посмотреть, что получилось — пока что мы всё равно не отрисовали наши карту.

Экран

Настало время вывести нашу карту на созданный экран:

Тем не менее, перед отрисовкой карты экран нужно инициализировать. Вернёмся к функции create():

Теперь при запуске проекта вы должны видеть случайную карту.

Click to view the game so far

Нажмите для просмотра результата.

Персонажи

Теперь займёмся персонажами: нашим игроком и его врагами. Каждый персонаж будет объектом с тремя полями: координаты x и y и хитпоинты hp.

Мы будем хранить всех персонажей в массиве actorList (его первый элемент — игрок). Мы также будем хранить ассоциативный массив с позициями персонажей в качестве ключей для быстрого поиска; это поможет нам, когда мы займёмся перемещением и боёвкой.

Мы создаём всех персонажей и рандомно размещаем их на свободных ячейках карты:

Настало время показать персонажей! Мы изобразим всех врагов буквой e, а игрока — количеством его хитпоинтов:

Возьмём только что написанные функции и передадим их в create():

Теперь мы можем увидеть размещённых на поле противников и игрока!

Click to view the game so far

Нажмите для просмотра результата.

Блокирующие и неблокирующие клетки

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

Перемещение и сражение

Наконец-то мы дошли до движухи! Так как в классических рогаликах персонажи атакуют друг друга при столкновении, мы обработаем это в функции moveTo(), которая принимает персонажа и направление (направление задаётся разностью координат x и y текущей и желаемой клеток):

Вкратце:

  1. Мы убеждаемся, что персонаж может переместиться в эту клетку.
  2. Если в ней есть другой персонаж, мы атакуем его (и убиваем, если счётчик его хитпоинтов достигает нуля).
  3. Если клетка пуста, мы перемещаемся в неё.

Заметим также, что мы выводим простое сообщение о победе после смерти последнего врага и возвращаем false или true в зависимости от того, валидно ли желаемое перемещение.

Теперь вернемся к функции onKeyUp() и изменим её так, чтобы при каждом нажатии клавиши мы стирали предыдущие положения персонажей (отрисовывая поверх них карту), перемещали игрока и снова отрисовывали персонажей:

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

Click to view the game so far

Нажмите для просмотра результата.

Простой ИИ

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

Заметим, что противнику неважно, кого атаковать: таким образом, при правильном размещении противники будут уничтожать друг друга, пытаясь догнать игрока. Прям как в классическом Doom!

Также мы добавили сообщение, которое выводится на экран при смерти игрока.

Теперь нам осталось сделать так, чтобы враги перемещались с каждым ходом игрока. Дополним функцию onKeyUp():

Click to view the game so far

Нажмите для просмотра результата.

Бонус: версия на Haxe

Изначально я писал это руководство на Haxe, кроссплатформенном языке, компилирующемся в JavaScript (и не только). Эту версию мы можете найти в папке haxe в исходниках.

Сперва вам потребуется установить компилятор haxe, после чего скомпилировать написанный в любом текстовом редакторе код, вызвав haxe build.hxml и дважды кликнув по файлу build.hxml. Я также добавил проект FlashDevelop, если вы предпочитаете пользоваться удобной IDE: просто откройте rl.hxproj и нажмите F5 для запуска.

Заключение

Вот и всё! Мы закончили создание простой roguelike-игры со случайной генерацией карты, движением, боёвкой, ИИ и условиями победы/поражения.

Вот некоторые фичи, который вы могли бы добавить:

  • несколько уровней;
  • бонусы;
  • инвентарь;
  • аптечки;
  • снаряжение.

Наслаждайтесь!

Перевод статьи «How to Make Your First Roguelike»