Имейте в виду: это не учебник или статья о передовых методах работы! Я просто описываю свой собственный опыт.

Тайловая карта для представления мира

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

Что такое тайловая карта? По сути, это весь игровой мир (карта), разделенный на ячейки (плитки). Как и настоящая карта, часто рисуется с указанием долготы и широты, чтобы упростить поиск определенных объектов. Еще одна хорошая аналогия - шахматная доска.

В любом случае, тайловая карта - это двухмерная сетка, вот что это такое. А что такое сетка для программиста? Что ж, это легко представить в виде массива. Играя в шахматы, вы можете описать положение любой фигуры: например, на изображении выше черный король находится на E8. Если бы мы представили эту доску как двумерный массив, мы могли бы указать на черного короля с помощью chessboard[4][7] - по сути, так же.

Итак, мы могли бы иметь двумерный массив, в котором каждый элемент был бы 1 или 0, и рисовать его на холсте, делая 1 представленной красной ячейкой, а 0 - зеленой. Или мы могли бы добавить больше цифр и цветов. Или рисовать не просто цветные прямоугольники, а шахматные фигуры. Или объекты мира: камни, деревья, дома ... В MDN есть очень хорошее введение к тайловым картам, проверьте его, если вам интересно.

Что хорошего в тайловых картах? Их легко реализовать! Они также позволяют упростить многие аспекты игровой механики, например обнаружение столкновений. Что плохого в тайловых картах? Что ж, мир выглядит не так естественно, не очень реалистично, ему не хватает глубины. Для решения этих проблем можно использовать другой вид геометрической сетки - изометрическую. Сетка, о которой мы говорили раньше, геометрически ортогональна, ее ячейки - квадраты. Изометрическая сетка имеет ромбовидные ячейки. Такие игры, как Fallout (не Bethesda Fallout, старые Fallouts 1 и 2), используют изометрическую проекцию - и это создает мир, который кажется гораздо более естественным (конечно, не таким естественным, как миры в играх с 3D-движком).

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

Тайловая карта в Keepers of the Orb

Хорошо, позвольте мне вернуться к своей игре. Я действительно озабочен тем, чтобы это выглядело реалистично? Неа. Мои спрайты ручной работы все равно будут выглядеть нереально! Чтобы изометрическая игра выглядела круто, ей нужны хорошие рисунки от профессионального художника, очевидно, это не мой случай. Кроме того, когда я мысленно думаю о лабиринте, я представляю его именно сверху вниз, в ортогональной проекции. А со строгой прямоугольной сеткой проще предугадывать путь врагов и планировать расстановку препятствий. Так что простейшая сетка мне показалась наиболее подходящей для моей игры TD.

Потом пришлось думать, как это реализовать. Честно говоря, массивы чисел мне показались не очень удобными. Я могу сделать так, чтобы каждое число представляло тип местности - например, 0 для травы долины (символы могут перемещаться через плитки травы) или 1 для гор, окружающих долину (символы не могут перемещаться через плитки гор). Но что, если я захочу добавить статические объекты, например камни? Должен ли я тогда иметь два массива: один для ландшафта, другой для объектов? Наверное, это могло сработать. Однако я решил представить каждую плитку как объект.

Объект может хранить столько данных, сколько вам нужно. Мне также было легче постичь мир, описанный через ячейки-объекты. Например, можно сказать, что ячейка Nr100 имеет травяной тип местности, имеет каменный объект, заблокирована и имеет соседей с определенными индексами. Также произвольно я решил использовать одномерный массив для хранения всех ячеек (на самом деле это не так важно, можно использовать двухмерный).

В конце концов, я реализовал свою тайловую карту вот так ... Я сделал ее самим объектом. Его ширина и высота будут зависеть от размеров окна, он должен занимать все доступное пространство экрана. Размер плитки (квадрат, поэтому ширина и высота совпадают). При запуске приложения создается карта листов. Он вычисляет, сколько плиток необходимо для заполнения экрана, создает такие плитки (с new Tile) и сохраняет их в массиве. Затем можно выполнить итерацию по массиву тайлов (или выбрать конкретный тайл с его индексом) и изменить его ландшафт, добавить объект, заблокировать его или нет и т. Д.

Да, на данный момент размер игрового мира и, следовательно, сложность будет зависеть от размера экрана - об этом я расскажу позже. Конечно, я мог бы создать тайловую карту с определенным количеством тайлов и добавить камеру, чтобы показывать игроку только часть мира (скорее всего, это делается во многих 2D-играх). Однако необходимость прокрутки карты в играх Tower Defense всегда казалась мне неправильной. Я хочу увидеть весь свой лабиринт сразу, хочу увидеть всех врагов. Поэтому я решил сделать игровой мир совсем маленьким, не больше окна (на больших экранах мог бы сделать его меньше). Это также означает, что мне не нужно сильно беспокоиться о производительности.

Игра жизни Конвея (с Tilemap)

Вышеописанную тайловую карту можно очень легко изменить. На самом деле я использовал его и для одного проекта Free Code Camp, для моей Игры Жизни.

Игра жизни Конвея - это симуляция, в которой у нас есть сетка ячеек, и каждая ячейка рождается, живет или умирает в зависимости от количества живых соседей. Итак, в моей первоначальной карте тайлов уже было почти все, что мне было нужно! У него даже была каждая плитка, чтобы знать своих соседей, но мне пришлось немного изменить это, потому что в воплощении игры жизни Конвея лучше учитывать, что все края сшиты вместе. Затем мне пришлось добавить поле alive к каждой плитке, очевидно, это могло быть true или false для каждой ячейки.

Теперь у моей карты тайлов был массив тайлов, поэтому без проблем создать функцию, которая будет перебирать этот массив, суммировать живых соседей для каждой ячейки и решать судьбу ячейки. Небольшая проблема заключалась в том, что если я изменю «живучесть» ячейки на лету - ну, эта ячейка является чьим-то соседом и для них вычисления будут отключены. Я решил это очень просто - добавил еще одно поле для хранения информации об изменениях и применил все изменения после. Конечно, этот игровой мир не очень сложен и может быть реализован с помощью массива простых чисел, 0 для мертвых ячеек и 1 для живых.

Я не особо беспокоился о производительности, но все же моя реализация не так уж плоха (лагает, когда вы делаете размер плитки 1 пиксель, но ну ладно), быстрее, чем некоторые React. Конечно, если для представления ячеек используются блоки div, это заставляет приложение работать с DOM на каждой итерации; Canvas ’requestAnimationFramemethod намного лучше справляется с визуализацией этих ячеек.