Спаунер мобов на Playmaker

Делать игры без программирования на юнити возможно, легко, просто и непринужденно. И я расскажу как это можно делать с помощью Unity Playmaker.

Ниже я расскажу о процессе работы с Плеймейкером в юнити на примере создания спавнера мобов.

Подготовительные работы

Плеймейкер работает по принципу скриптов — на игровой объект можно навесить сколько угодно компонентов плеймейкера (далее я буду именовать их как скрипты FSM, как сокращение от Finite State Machine), и в этих скриптах описать логику. Однако прежде чем мы начнем, для начала опишем логику результата, который хотим получить.

Планируется, что мы создадим универсальный спавнер мобов, который будет настраиваться следующим образом:

  • Мы сможем определить, будет ли спавнер повторно спавнить моба или нет.
  • Расстояние, на котором спавнер будет реагировать на игрока
  • Задержка повторного спавна моба.
  • Массив мобов, которых может заспавнить спавнер.

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

Для начала нам понадобится префаб, который будет выступать непосредственно спавнером, который мы будем ставить на уровне. Условимся, что делаем спавнер для ЭКШЕН ММОРПГ с видом от третьего лица. Я сделал вот такой агрегат:

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

С подготовкой покончили, приступаем к Визуальному Скриптингу.

Логика спавнера на Playmaker в Unity

В родительском объекте нашего префаба добавляем компонент Playmaker FSM, именуем его к примеру «Spawner», и жмем Edit.

Небольшая ремарка, для тех кто первый раз. Плеймекер работает по принципу конечного автомата. Скрипт плеймекера это простейший автомат, в котором пошагово выполняется набор правил, по одному правилу за кадр. Правило в данном случае корректно именовать Состоянием (буду сокращать до стейта), и в рамках стейта скрипт может выполнять N-ое количество действий, которые в плеймейкере корректно именовать Экшенами. Это простейшая концепция и не должна вызвать проблем для тех, кто смог в принципе скачать и открыть Юнити.

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

Не пугаемся, на экране нам показывает пустой стартовый стейт, то есть то состояние, в котором будет находиться игровой объект в момент инициализации. Хорошим тоном будет запихнуть в этот стейт всё, что нам нужно инициализировать, чтобы потом использовать в дальнейшем. Однако прежде всего, мы инициализируем лапками все необходимые нам переменные, которые будем использовать для настройки спавнера. Для этого идем в верхний правый угол скрина во вкладку Variables. Видим внизу инпут и селектор типа переменной. С помощью мозга и нехитрых движений пальцами, добавляем 4 переменные: две типа Float, одну булеву, и одну массив игровых объектов.

Когда мы создали переменную массива, нам нужно еще выбрать тип этого массива.

Получился набор переменных, которые мы осмысленно(!) именовали, и думаю смысл всем понятен. Через float переменные будем регулировать дальность обнаружения спавнером игрока, и время респауна моба, через булеву — сам респаун, и в массив будем загонять мобов. Ах да. Важный фокус.

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

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

Первое важное правило плеймейкера — чистота. Скрипт должен быть читаем. Я работаю над одним проектом почти год, и прерывался на месяцев 5. После возвращения я был сам себе благодарен за то, что умудрился именовать все стейты по смыслу, и во многом это способствовало тому, что проект в итоге не оказался на помойке. Поэтому лучше сразу приучать себя к тому, что каждый стейт должен иметь осмысленное название, и в идеале — цветовой код. Плеймейкер всё это позволяет, поэтому я буду говорить об этом, как о норме и данности.

Первый стейт как я уже говорил — стартовый. В нем может происходить что угодно, но я предпочитаю в этом стейте инициализировать что-то, что в дальнейшем нужно будет использовать. Поэтому именую его всегда как INIT и крашу почему то в фиолетовый. Далее делаем следующее — определяем позицию спавнера, и корректируем её, чтобы моб нормально спавнился и не падал случайно под террейн в нашей ММОРПГ. В окне экшенов (которое открывается по кнопке Action Browser) ищем два экшена — сначала Get Position, а потом Vector3 Add XYZ. И настраиваем их как на скрине.

Расшифровываю. Сначала мы опредяем позицию нашего объекта (через Use Owner — имеется ввиду овнер скрипта), и сохраняем в переменную Spawned Position. Изначально переменной там нет, но мы нажимаем на дропдаун и выбираем пункт «New Variable…» . Обязательно ставим Space = World, чтобы нам вернуло нужные координаты. А вторым экшеном в этом стейте (то есть еще одним действием в пределах одного кадра) мы чуть корректируем Z координату для созданной нами переменной, добавляя к ней поллитра. Таким образом, у нас в Spawned Position будет значение X, Y, Z+0.5. То что надо.

Мы инициализировались, и стейт можно завершить. Для этого жмем ПКМ на стейте и выбираем Add transition — FINISHED.

Тем самым мы добавили событие перехода — мы объяснили автомату, что когда все действия в пределах стейта выполнены, мы его завершаем и можем перейти к следующему стейту. Создаем новый стейт через ПКМ, в котором мы будем проверять нашу первую настройку спавнера. Для этого в новый стейт добавляем экшн Bool test и выбираем в этом экшене переменную Repeatable, которую мы создали в самом начале. Теперь указываем событие (эвент так называемый) если переменная True и если она False — для этого выбираем в дропдаунах нижний пункт «New Event» и именуем осмысленно. Для тех кто умненький — да, это простейшая конструкция if/then/else. Для тех кто не очень умненький — не забудьте протянуть макаронину от FINISHED к новому стейту.

Мы создали 2 эвента, которыми может завершиться стейт, добавим их в качестве переходов в наш стейт через ПКМ, также, как добавляли FINISHED.

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

Мы пока не обращаем внимания на красный стейт — на скрине начинка зеленого стейта.

Первым делом мы обращаемся к заранее созданному нами массиву Mobs и берем из него случайный элемент. Если элементов будет всего 1 — то его и возьмет. Если больше — то выберется случайный. После этого, мы фиксируем выбранный элемент в виде локальной переменной (создаем также через пункт дропдауна New variable), именуя её SelectedEnemy. Далее мы непосредственно создаем новый игровой объект того типа, который мы взяли в массиве — то есть буквально создаем SelectedEnemy. И создаем его в той самой точке, которую мы определили в самом первом стейте. И записываем созданный объект в новую переменную (можно кстати в старую, не возбраняется и будет работать), которую именуем как SpawnedEnemy. И последним экшеном задаем SpawnedEnemy случайное вращение по оси Y, чтобы наш моб при появлении смотрел в произвольную даль.

Всё.

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


Вернемся на два стейта назад. Когда спавнер определялся, как он работает — многоразово или нет. И теперь сделаем ветку посложнее — многоразовую.

Делаем новый стейт. Для него нам понадобиться кастомный экшен Set enemy FOV by Tag, который мы возьмем из экосистемы Плеймейкера. Это такой «гитхаб», библиотека экшенов, которые делает коммьюнити. Обычно, если что-то нужно специфичное — там оно уже есть. Лезем в экосистему и через поиск находим нужный экшн, жмем Get. У меня уже этот экшен стоит. После компиляции, в экшен браузере плеймейкера появится новый экшен который можно будет также через поиск найти в списке. Добавляем его в стейт.

Настраиваем как на скрине. Вкрациях — мы говорим здесь автомату, что мы хотим, чтобы наш спавнер (use owner как мы помним), мог в радиусе дистанции, которая у нас выведена как переменная настройки, определял по кругу (Field of view может работать от 0 до 360 градусов, это фактически скрипт Глаз для объекта, то есть если мы ставим для FOV 360 градусов, значит у объекта будет обзор по кругу), определять игровой объект с тегом Player и в случае если объект попадает в обзор, отправляем событие Spawn.

Обязательно ставим галочку Every Frame — в этом случае стейт будет проверяться каждый кадр, пока в обзор спавнера не попадет игровой объект с тегом Player. Спавнить моба таким образом «дешевле» для сцены, нежели держать игровой объект созданным с самого начала исключительно потому, что выполнять проверку наличия игрока в поле зрения проще, чем держать созданным игровой объект со всей его внутренней логикой.

Добавляем переход Spawn для этого стейта (ПКМ на стейте, Add transition и тд), и добавляем новый стейт, который начнет выполняться когда спавнер увидит игрока в поле зрения. На деле можно скопировать стейт Spawn Enemy из соседней ветки — потому что мы по сути спавним моба, когда видим игрока. Все идентично.

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

Делаем дальше два стейта. В первом ставим экшен Wait — и используем последнюю переменную настройки, которой определяем время задержки респауна.

А во втором стейте мы проверяем жив ли наш моб, которого мы спавнили, или нет. Для этого используем экшен Game Object is Null — здесь важная оговорка: мы предполагаем, что моб умирая, удаляется из игры. Если используем пулинг мобов, то нам нужно делать другую проверку (активен ли игровой объект). Когда моб умирает и мы удаляем его из игры, наша переменная SpawnedEnemy оказывается пустой. В данном случае это подходит нам для проверки.

Если моб жив, то переход будет через событие Wait, и нас вернет на предыдущий стейт еще подождать респавна. Еслиже в переменной пусто — то переход будет через событие Respawn, для него создаем новый стейт, в котором мы проверим, можем ли мы респавнить вообще моба, ведь по условию выше мы хотели респавнить моба когда игрок не находится возле спавнера, чтобы моб внезапно не появился перед игроком из пустоты. Поэтому в следующем стейте мы проверяем через экшен Game object Visible to Camera, видит ли наша игровая камера спавнер вообще или нет.

Здесь у меня в переменной GAME CAMERA определен игровой объект камеры, сама переменная глобальная. Но вы можете перетащить просто объект камеры со сцены в поле Camera и всё. Это не плохо, но не масштабируемо в перспективе (лучше глобальные объекты инициализировать в глобалки, к примеру камеру, интерфейс, игрока и тд.). В этом стейте мы заставляем автомат проверить, виден ли владелец скрипта для игровой камеры, и если да то немного подождать (переход на стейт Wait 1 sec в котором экшен Wait).

Если же спавнер не виден для камеры — то делаем переход через событие Respawn на стейт, в котором «высматриваем» игрока. Круг замкнулся.

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