Knighttron. Ландшафтные приключения

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

И так, я приступил к программированию и все шло хорошо и плавно, пока я не получил первый сгенерированный ландшафт.

Ландшафт обыкновенный

Ну как! Как на таком ландшафте можно устроить массу приключений!? Как его разнообразить, используя три вида деревьев, три вида камней и несколько видов тайлов? Похоже приключения и опасности вот-вот накроются медным тазом, так как игровой ландшафт представляет собой унылое однообразие. Но решение не заставило себя долго ждать, тем более оно уже применялось успешно в других играх: нужно позволить тайлам земли чуть-чуть двигаться по высоте, таким образом, используя одни и те же тайлы, можно создавать бесконечно разнообразный ландшафт. Этот вариант казался достаточно простым и понятным в реализации. Но это тот самый снежок, который в итоге начал расти в большущий снежный ком.

Динамический ландшафт

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

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

Динамический ландшафт

В итоге мне пришлось придумывать небольшую сортировочную систему и переписывать всю основу игры. Обратите внимание, тут я переписал игру первый раз, благо работы сделано было не много и все, что не касалось игровой карты, перекочевало в новую игру через копи/паст.

Сортировочный движок

Это был первый вызов, который заставил меня серьезно повозиться. После небольших исследований и копаний материалов по теме, я пришел к тому, что для каждого объекта необходимо создать OrderBox — он будет определять прямоугольное основание объектов, согласно его размерам и расположению в пространстве будет вычисляться его сортировочный индекс для объекта. Звучит немного сложно, но на деле все просто.

Пример сортировочного бокса

Посмотрите внимательно на картинку — эти синие прямоугольники и есть те самые OrderBox’ы. Если бокс персонажа будет выше бокса дерева, то персонаж окажется за деревом, а если ниже, то перед деревом. «ХА!» — возможно скажете вы, «тут ведь можно обойтись и одной координатой Y, которая с легкостью сделает все тоже самое». Но все немного иначе, благодаря формуле вычисления индекса сортировки. Я написал небольшой класс, который реализует работу OrderBox, а формула вычисления индекса сортировки из коробочки достаточно проста:

order = top + height;

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

order = top + height - (positionX / CELL_WIDTH);

В любом случае все сводилось к тому, что я понимаю как это работает и при необходимости смогу легко поправить формулу сортировки одним махом для всех объектов или ввести еще какие-либо условия и правила.

В итоге, принцип работы OrderBox идентичен сортировке по Y, но есть возможность задавать толщину объекта, а так же просто регулировать положение бокса относительно нулевой координаты объекта. Этот подход помог мне радикально решить проблему сортировки и позволил легко настраивать сортировку новых объектов без пляски с магическими числами.

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

  • X — положение бокса относительно родительского объекта по X;
  • Y — положение бокса относительно родительского объекта по Y;
  • width — ширина бокса;
  • height — высота бокса;

Грани Top, Right, Bottom и Left рассчитываются следующим образом:

  • top = object.y + y;
  • right = object.x + x + width;
  • bottom = object.y + y + height;
  • left = object.x + x;

Что же случится, если боксы разных объектов пересекаются? Ничего страшного не случится, следуя формуле: top + height — на переднем плане всегда будет тот объект, чья нижняя грань ближе к нижней части экрана.

Пример наложение боксов

Так что, оперируя толщиной бокса, можно задавать «жирность» или важность объекта.

Еще важное уточнение: на OrderBox не должна действовать координата Z, чтобы поднимаемые объекты в пределах своей клетки всегда правильно рисовались. То есть OrderBox остается внизу, словно тень под поднятым объектом.

Пример поднятого объекта

На изображении дерево поднято по Z вверх, но при этом OrderBox остается там, где дерево находится фактически на игровой карте по Y и Z, чтобы корректно рассчитать сортировочный индекс относительно других объектов.

Как работает координата Z в 2D

Реализация третьей координаты достаточна проста и суть её заключается в том, что она лишь отражает как высоко находится объект над землей. Обычные координаты X и Y влияют на положение объекта в двумерном пространстве, в моем случае — это плоскость земли. Таким образом, используя положение объекта по X и Y я могу как обычно посчитать в какой ячейке находится объект, а так же выполнять любые другие действия. А вот при рендере объекта для его графического представления складываю Y и Z, чтобы получить его реальное положение на экране.

sprite.x = position.x;
sprite.y = position.y + position.z;

Немного о редакторе

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

Как-то так выглядит редактор в игре

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

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

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

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

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

Другие записи из этой серии

  1. С чего все начиналось
  2. Ландшафтные приключения
  3. Анимация персонажей
  4. Поиск пути
  5. Физика в Knighttron
  6. Продолжение следует...

 


Индикаторы: Уроки, Разное
Постоянная ссылка

 

 

Прикольно :) Ждем продолжение рассказа с видео :)

TheRabbit
18 Октября 2014
— 16:28
#

Здорово!!! Очень интересно узнавать о ваших изобретениях! Жаль статья короткая)

samana
18 Октября 2014
— 17:09
#

Очень не хвататет скрина с динамическим ландшафтом!

Kickerua
18 Октября 2014
— 17:09
#

Очень, очень интересно! Спасибо за новую запись!

Рокзеро
18 Октября 2014
— 17:23
#

А как жеж старлинг и мобильные платформы?

ShockWave2048
18 Октября 2014
— 17:30
#

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

Ant.Karlov
18 Октября 2014
— 19:00
#

@ShockWave2048, если говорить о мобильных платформах, то я бы предпочел что-нибудь нативное, или Unity3D. С Flash мне как-то не очень хочется идти на мобилки.

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

Ant.Karlov
18 Октября 2014
— 19:17
#

Тоже делал z -сотировку ломал голову пару дней, но в итоге все оказалось довольно просто. У меня зомби шли по земле и я делал сортировку по нижней грани зомби-прямоугольника (zombie.y+zombie.height/2) чтобы была возможность делать врагов разного роста.

Вадим
18 Октября 2014
— 19:22
#

@Ant.Karlov спасибо за статью. Как всегда интересно и красочно.
Думаю, если добавить освещение, то можно дополнительно разнообразить уровни. Даже если оно будет статичным

icewind
18 Октября 2014
— 23:55
#

Есть более общий способ виртуальной z координаты, я тоже делал так когда-то давно, но так не сделать двухэтажный домик, например, чтобы и на первом и на втором этаже бегать. В общем в одних из первых Sims было сделано правильно, там нужны логические координаты объекта в трехмерном пространстве, функции перевода их в координаты экрана, а сортировка делается одной строчкой по логической координате z и координатами на экране итоговыми ScreenX ScreenY. Получается правильный метод сортировки для всех случаев жизни. Чтобы прорисовать несколько объектов на одной высоте просто в логическую координату z добавляют очень мелкое число (0.0001), на экране положение вертикальное никак не отразится, а вот отображаться объект будет уже выше, чем такой же на этом уровне. Но это все для более сложных задач, для вашей игры описанный метод годится, но он медленный.

Greeg
19 Октября 2014
— 16:09
#

Хорошо бы еще Blending (смешивание тейлов) реализовать, немного красочнее смотреться будет, хотя и так тейлы давольно-таки оригинальные

Greeg
19 Октября 2014
— 16:12
#

@Greeg, да, для более честной сортировки я бы уже смотрел в сторону изотермических движков. Я не планирую создавать на одной локации несколько этажей, хотя это кое-где будет реализовано в фэйковом виде. Я только не очень понял с чего вы решили что данный способ сортировки медленный? :)

Ant.Karlov
19 Октября 2014
— 17:41
#

Этот принцип не привязан к изометрии, в изометрии просто формулы перевода другие в экранные координаты. Про скорость да... вы не писали как непосредственно в коде реализовали, можно описанный способ реализовать быстрым для flash методом.

Greeg
19 Октября 2014
— 18:16
#

Greeg, можно отсекать выходящие за экран тайлы. И тогда любая сортировка будет довольно шустро работать. Сколько тут. Тайлов 100 на экран поместиться. Смотря какое разрешение. Тем более можно "пол" просто статическим сделать. Ну и можно сортировку по fps размазать на пару кадров.

Ant.Karlov, кстати какое решил выбрать разрешение и FPS для этой игры? Сейчас на флеше замечаю тенденции в сторону 60 FPS и 700x525px.

Avix
19 Октября 2014
— 21:18
#

@Avix, в моем случае сортировка применяется для всех игровых объектов на экране за исключением интерфейсов.

Разрешение взял 720x480. После того как сильно «обжегся» с Fire Catcher в плане производительности, решил не выеживатся и ориентироваться на 35 FPS. Надо оставить запас производительности под баннеры и т.п. Судя по моим предыдущим работам, этого достаточно для комфортной игры :)

Ant.Karlov
20 Октября 2014
— 03:34
#

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

Greeg
20 Октября 2014
— 08:24
#

@Greeg, забыл написать про тайлы: чуть позже я объясню почему тайлы именно такие и почему им не нужно смешивание :)

Ant.Karlov
20 Октября 2014
— 13:59
#

Привет, Антон! Начал играться с AntHill`ом и обнаружил, что FD кидает исключения при альтернативном способе объявления вектора:

var a:String = new String["a", "b"];

Оно у тебя в коде используется.
Погуглил немного, оказалось, что это как-то связано с Flex SDK. Только я не пойму, что дальше делать, чтобы он это компилировал. Настройки компилятора поменять? ЧЯДНТ?

dmp
20 Октября 2014
— 20:31
#

Отлично! Спасибо за статью Антон!

Vacsa
20 Октября 2014
— 22:13
#

@dmp, вектор инициализируем так:

var a:Vector.<String> = new Vector.<String>();

Или так:

var a:Vector.<String> = new <String>[ "a", "b" ];

Ваш пример инициализации вектора не верный. Если вы нашли такой способ инициализации у меня в коде, пришлите пожалуйста название класса и номер строки чтобы я проверил. Спасибо!

Ant.Karlov
21 Октября 2014
— 14:09
#

@Ant.Karlov, прошу прощения, конечно <String>[].

Спасибо за ответ. С проблемой разобрался - я компилировал в версию плеера 11.0. Указал 11.9 в настройках проекта, и всё заработало.

Отличный движок, кстати говоря. Наличие отладочной консоли порадовало :)

dmp
21 Октября 2014
— 19:08
#

"Игра казалась простой и понятной в плане разработки ровно до тех пор, пока я не начал все усложнять"
as3isolib, меняем один параметр и работаем с честными 3-мя координатами. Профит:)

Lorein
22 Октября 2014
— 12:48
#

Антон и друзья блога, вот новость по Unity3d http://kanobu.ru/news/oleg-pridyuk-pokidaet-unity-technologies--374499/ .
Конечно это нисколько не показатель ничего, но показатель того что что-то меняется, следите за новостями и конечно держите запасной парашют в виде нативных разработок.
Успехов Антон!

Vacsa
22 Октября 2014
— 14:34
#

@Lorein, as3isolib неплохо написан, но тормозной жутко, а вторую часть автор что-то забросил.

Greeg
23 Октября 2014
— 16:34
#

@Greeg, ничего там не тормозит, всё отлично. Просто по дефолту стоит дисплейЛист вместо блиттинга. Нужно правильно готовить спрайты (растерезировать), как и в любой игре.

А преимущества as3isolib очевидны. И не пришлось бы тратить время на сомнительные алгоритмы с толщиной основания.

Lorein
23 Октября 2014
— 21:01
#

@Loerin, да, если бы я знал как все в итоге обернется, то вероятно я бы ознакомился с изометрическими движками, но сейчас уже слишком поздно :)

Ant.Karlov
25 Октября 2014
— 19:52
#

Антон, делай игру на AIR и своём движке!
TheRabbit даёт рекомендации:

http://gamespoweredby.com/blog/2013/12/recommendations-for-adobe-air-mobile-developers/

User_User
27 Октября 2014
— 17:47
#

Антон, ты сортируешь тайлы каждый кадр или только когда какой-то объект передвинется?

User_User
27 Октября 2014
— 18:03
#

Чем-то напомнило эту игру http://onlinebrodilki.ru/ohotnik_za_golovami

Жду продолжения с нетерпением.

Ваня
8 Ноября 2014
— 16:59
#

Ты полный Ваня!

Vacsa
9 Ноября 2014
— 16:56
#

Антон, пиши еще! =)

Кирюшиноченочик
19 Ноября 2014
— 08:49
#

Пора бы делать под мобилки :).

WeslomPo
21 Ноября 2014
— 10:51
#

1486

Пора бы делать под мобилки :
2 Декабря 2014
— 11:31
#