SETI | FPS Квест на Unity без кода - пост #5

SETI | FPS Квест на Unity без кода - пост #5

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

Вводные

За плечами N лет в разного рода конструкторах и редакторах игр, в которых конструирование игровой логики сводилось к базовыми триггерам и событийным конструкциям из условных операторов. Начиная от VHE, WC3 WorldEditor и заканчивая Gamemaker и Construct 2. Кроме того - сомнительного качества бэкграунд кодинга на Delphi всяких патчеров и генераторов, двухнедельное увлечение питоном для написания телеграм-бота, ну и по мелочи веб-опыт в верстке и JS без каких-либо твердых вообще примеров.

Попытки начать что-то делать на Юнити как на 3d движке были неоднократные и ранее, однако каждый раз они упирались в необходимость что-то кодить. Сишарп мне не поддавался и каждый раз подход срывался. В какой-то момент я написал "клевый" эмбиент трек который спровоцировал меня еще раз попробовать ворваться в юнити, сделав простой квест бродилку с минимальным набором игровых механик - я тогда плотно сидел на всяких индиквестах накаченных с итча. В итоге появилась заготовка игрового мира, которая настолько лаконично получилась, что стало интересно - а получится ли без кодера со стороны довести её до ума, и не бросить. Я догадывался, что это возможно, но не знал как именно.

Я стал искать решения и нашел.

Playmaker

Я знал про блюпринт-системы и ранее, и уже сталкивался с ними где-то, но понял ничерта на тот момент. Кажется это был Godot.

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

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

По сути плеймейкер - это Finite-state machine, то есть модель конечного автомата(алгоритма), выполняемого при заданных условиях.

Если вы привыкли к работе с условными операторами и циклами, то использование системы конечных автоматов будет первое время для вас непривычно, потому что придется привыкнуть делению событий на составные части, так называемые "состояния". Суть их работы тоже можно описать структурой "если-то-иначе", но fsm - менее линейно на мой взгляд.

Разберу на примере.

Есть объект - записка. Или письмо какое-нибудь. Мы хотим дать игроку возможность прочитать его. Для игрока мы создаем автомат (fsm триггер, их у объекта может быть N штук). Дальше строим логическое рассуждение на языке автомата.

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

Первое состояние автомата
Первое состояние автомата

После этого, игрок (не записка!) переходит в новое "состояние" - в новом состоянии нам нужно проверить, что происходит перед игроком, с чем он пытается взаимодействовать. Для этого используется метод Raycast.

Что такое рэйкаст - это когда мы "стреляем" лучом, который позволяет нам определить несколько параметров объекта, с которым он столкнется. Это некий невидимый проверочный луч.

Игрок вступает в состояние проверки объекта перед собой, после нажатия клавиши, и выпускает единоразово луч определенной длины (мы его не видим).

Второе состояние автомата
Второе состояние автомата

Когда луч выпущен, фиксируем результаты: записываем в переменную объект, с которым луч столкнулся, и инициирует событие, если столкновение с объектом вообще имело место быть. Это событие выводит нас из второго состояния и переключает на третье, новое состояние, в котором мы должны проверить, что за объект вообще это был.

Небольшая ремарка - данная логика не претендует на однозначность в плане эргономики: кто то может решить что в одном fsm с таким успехом можно было описать все взаимодействия с объектами после нажатия E. Да, можно. Но управлять и исправлять такую обросшую конструкцию менее эргономично на мой взгляд.

Здесь, в третьем состоянии автомата, логика простая - условные операторы. В первую очередь(первым шагом), попадая в это состояние мы получаем имя объекта с которым взаимодействовали (все записки в игре у нас уникальные, и имена потому у них как у игровых объектов могут быть уникальными) и пишем его в переменную.

Второй шаг - сравнение переменной с определенным названием. Если совпадает - идем в 4-ое состояние, если нет - вызываем событие Finished и тем самым говорим автомату, что работа окончена, возвращаемся в исходное состояние - первое(где ожидаем нажатия клавиши опять).

Третье состояние автомата
Третье состояние автомата

Четвертое состояние в моем случае - проверка языка игры. В глобальной переменной я храню строковое значение - RU или EN, и в зависимости от этого показываю результат на нужном языке. Логика простая - "Если строка глобальной переменной LANGUAGE равна RU то запускаем событие RUSSIAN, если нет, то запускаем событие ENGLISH."

Четвертое состояние автомата
Четвертое состояние автомата

Пятое состояние автомата это непосредственно действие открытия письма для игрока. Здесь важно соблюсти логику(обратите внимание на небольшие треугольники под каждым действием, означающие соблюдение порядка выполнения в рамках этого состояния). Мы должны показать письмо игроку (у нас это заготовленный объект в UI канвасе), обездвижить игрока (чтобы нельзя было бегать по миру пока читаем письмо) и ожидать нажатия повторного клавиши Е.

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

Пятое состояние автомата
Пятое состояние автомата

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

Последнее состояние автомата
Последнее состояние автомата

Профит. Теперь игрок может читать записки, а мы можем добавлять их в этот автомат в неограниченном количестве, разделяя их по названию игрового объекта. Вот как это выглядит в игре:

Тут случайный затекстуренный куб в виде письма)

Вариантов исполнения этой логики такими автоматами уйма, зависит от желания.

Если интересно будет, потом расскажу еще про какую-нибудь логику, реализованную через Playmaker FSM, на данный момент есть логики:

1) Открывания дверей на петлях (на заглавном скрине)

2) Кодовый замок для 6 значных кодов

3) Перетаскивание предметов с места на место

4) "Чтение" входящих сообщений\

5) Подсчет собранных предметов

6) Показатель запаса здоровья с регенерацией (и хромающей системой урона от падения).

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

3737
30 комментариев

А по производительности как, нужен  топовый процессор? )

Все эти FSM на тыкательных мышкой механиках генерируют на полпроекта тяжелого мусора с рефлексиями, lua-парсерами-интерпретаторами и прочим, после чего игра утопает в бесконечных сборках мусора и неоптимизрованных миллионах Update.

4
Ответить

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

Ответить

А в чем смысл делать игру на unity, на визуальном скриптинге, когда есть движок, где эта система с 2014 года и реализована в разы лучше и который впринципе лучше? Если ты планируешь устраиваться в студию, то учи C#, на визуальном программировании ни одна студия игры не делает, а если ты для себя, то советую попробовать перейти на анрил  

3
Ответить

На визуальном делают, Blueprints в UE уже стали ходовыми в игровых студиях. Точно помню, что какая-то успешная VR игра была полностью на нём разработана и только ей дело не ограничивается. Поэтому Unity и подсуетились, выкупив разработчиков Bolt с потрохами – т. к. в будущем это неизбежно станет стандартом.

1
Ответить

причин уйма.
1) объективно слабое железо.
2) графоний не преследую.
3) тонны бесплатного контента и понятная простая структура и иерархия его использования.
4) субъективная понятность именно этого движка ввиду определенного раннего знакомства с ним.
5) тонны туторов на любой вкус и цвет.
6) навык самостоятельного генерирования контента для именно этого движка
7) удобный и понятный плеймейкер
8) так склалось

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

Ответить

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

 Второй шаг - сравнение переменной с определенным названием. Если совпадает - идем в 4-ое состояние

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

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

И это мы ещё не говорим о том, чтобы всем запискам назначить отдельный слой и делать рейкаст только по этому слою, чтобы «не задевать» другие объекты и не делать лишние проверки.

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

3
Ответить

"на каждой записке должен быть компонент"

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

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

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

Ответить