Создание игры на ваших глазах — часть 8: Визуальное скриптование кат-сцен в Unity (uScript)

В одной из предыдущих публикаций я рассказывал, что мы прикрутили к нашей игре язык Lua для скриптования различных сценок. Однако, попользовавшись им какое-то время, мы поняли, что порой написание таких скриптов превращается в довольно сложночитаемый и сложноотлаживаемый код.1fc6394199b74a39a547e40b4b42e38e.pngИ мы задумались о визуальном подходе. В этой статье я расскажу о нашем знакомстве с средством визуального скриптинга для Unity — «uScript», о его возможностях и расскажу о нашем опыте.Да, на скрине выше — реальные скрипт и схема.

Введение. Итак, давайте для начала посмотрим на то, что было. Ниже приведен реальный скрипт, создающий двух персонажей на экране, рисующий простенький диалог, дающий юзеру выбор из 2х вариантов и ветвящийся в этом месте.Исходник LUA-скрипта vhs.HUD (0) vhs.SwitchZone («street») local c1 = CharacterGfx () c1.create («c1», «char_big») c1.mirror (0) c1.setpos («n_2») c1.animate («f_idle»)

local c2 = CharacterGfx () c2.create («c2», «char_black») c2.mirror (1) c2.setpos («n_3») c2.animate («f_idle») c2.preset («opp_lmb»)

char.animate («idle») char.mirror (1) char.setpos («n_1»)

c1.say («I need your clothes, your boots and your motocycle») c1.wait_bubble () c2.say («Yep!») c2.wait_bubble ()

char.animate («f_idle») char.mirror (0)

vhs.ShowMultiAnswer («Try to catch me! (run away)», «No way! (start fight)»,») switch_answer { case 1: vhs.BlackScreen («You are not fast enough to run away. So Have to fight!») vhs.StartFight (77,7) end,

case 2: vhs.StartFight (77,7) end, } В игре это выглядит так: 4c83aca017ea48bcb84ded8394d7d1a5.pngВ принципе, в скрипте выше нет ничего страшного. Но представьте, что у вас не 1 ветвение, а два. Представьте, что вам нужно проверять какие-то игровые параметры и ветвить скрипт исходя из них. Это очень быстро может стать ненаглядным.

Именно в такой момент нам остро захотелось визуализации.

Посмотрев несколько плагинов для юнити, мы остановились на uScript. Он очень мощный, гибкий, и при этом просто расширяемый. Кроме того, он создает минимальный impact по быстродействию, т.к. на этапе сохранения схем сразу же компилит их в C#, т.е. для Unity скрипт собранный в таком редакторе не очень отличается от скрипта, написанного руками на шарпах.

Давайте сразу приведу скрин того, во что превратился вышеприведенный LUA-скрипт. (картинка кликабельна)

ca89721dcc7740d6aa33858e3c81bb7b.png

Выглядит немного громоздко, но зато сразу наглядно. Когда, кто и где создается, что делает, а главное видны ветвления.

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

c06e80aaa2724ffda4dfeb413065f3ff.png

А на схеме — так:

8f93300c284e4414b834cde05fc8afea.png

И сразу видно, что произойдет при выборе ответа №1 и ответа №2. А если таких ветвлений будет больше — то тем более схема не потеряет наглядности.

Принципы uScript. Давайте быстро пробежимся по тому, из чего состоит схема. Собственно, основные модули (в терминологии uScript они называются «nodes») — это событие (с него обычно начинается скрипт или цепочка), action и переменные.6ba1449c9d334086afeeffa75782fd89.png

У action’он есть вход (обычно 1) и выход (ы). Например, у самого простого действия 1 вход и 1 выход. А у какого-нить блока условия — уже будет два выхода, например.

Снизу блока подключаются переменные. Треугольник означает, что в переменную будет произведена запись (output).

Например, в этом примере мы создаем персонажа (с помощью блока «Create char»), а потом выставляем ему же зеркальность в «true» (с помощью блока «Mirror»):

8cb4e24e381b434f9dcf01a4598b8ea4.png

Кстати, все переменные могут иметь названия (в нашем случае «с1»). И все переменные одного типа с одинаковым названием будут синхронизированы в пределах одного скрипта (схемы). Т.е. пример выше совершенно идентичен такому:

fffc7a3ea5c840ddac6ee84763a52d96.png

Сделано это чтобы избавить вас от необходимости тянуть связи через два экрана.

Кроме того, если поставить галочку «expose to Unity», выбранная переменная станет public и будет видна другим скриптам (как визуальным, так и вашим рукописным). Массивы так же поддерживаются.

Немного практики. Все модули, которые вы видите на схеме — самописные. И были написаны за 1 вечер. Давайте посмотрим на их код.Рассмотрим сначала что-нибудь очень простое. Например, action, который называется «Start fight». Он начинает бой (по сути, вызывает метод игровой логики) и принимает два параметра — айдишник боя и айдишник соперника.

183671f522a3484cb03f13a75f98cf97.png

Код для него:

[NodePath («Actions/VHS Story/Fight»)] [NodeCopyright («Copyright 2014 by GameJam»)] [NodeAuthor («GameJam», «http://www.gamejam.ru»)] [FriendlyName («Start Fight»,»)] public class uScriptAct_StartFight: uScriptLogic { public bool Out { get { return true; } } public void In ( [FriendlyName («Opp. id»,»)] int opponent_id, [FriendlyName («FightData id»,»)] int fightdata_id ) { MainGame.me.StartSimpleFight (opponent_id, fightdata_id); } } Просто? Очень.А теперь давайте усложним. Допустим, мы хотим проиграть какую-либо анимацию. И хотим иметь два выхода. Один — сразу, а второй, который запустится только когда анимация проиграется до конца.

1df372e20e0f40739d4d878ffdfe75a1.png

Справа вы можете видеть блок с конфигурацией блока, куда вы вбиваете значения. У блока 3 входных параметра — CharacterGfx (непосредственно персонаж, которому мы проигрываем анимацию), Animation (название анимации) и Mirror (необходимость зеркаленья). И у блока есть два выхода: Out (выход сразу же) и Finished (только когда анимация закончится).

При этом переменная «Mirror» является энумератором с параметрами «да», «нет» и «не менять», которая представляется в виде dropdown-списка в окне свойств.

Код особо сложнее не стал:

using uScriptEventHandler = uScript_GameObject.uScriptEventHandler;

[NodePath («Actions/VHS Story/Character»)] [NodeCopyright («Copyright 2015 by GameJam»)] [NodeAuthor («GameJam», «http://www.gamejam.ru»)] [FriendlyName («Char: Play anim»,»)] public class uScriptAct_CharacterPlayAnimation: uScriptLogic { public bool Out { get { return true; } }

[FriendlyName («Finished»)] public event uScriptEventHandler Finished;

public enum BooleanSet { NoChange = 0, True, False } public void In ( [FriendlyName («CharGfx», «The CharacterGfx.»)] CharacterGfx ch, [FriendlyName («Animation»,»)] string anim_name, [FriendlyName («Mirror»,»)] [SocketState (false, false)] [DefaultValue (BooleanSet.NoChange)] BooleanSet mirror ) {

ch.PlayAnimation (anim_name); if (mirror!= BooleanSet.NoChange) ch.SetMirror (mirror == BooleanSet.True); ch.OnAnimationEndedCallback += () => { if (null!= Finished) Finished (this, new System.EventArgs ()); }; } } Еще момент. Во всех блоках выше выход (Out) вызывался сразу же после выполнения кода блока.А что если мы хотим сделать асинхронный action? Например, загрузку сцены. И чтобы выполнение нашего визуального скрипта приостановилось до того момента, пока асинхронно не прогрузится сцена.

Делается это так же просто. Вместо строчки

public bool Out { get { return true; } } которая являлась флагом «скрипт всегда готов к выходу», мы пишем: public event uScriptEventHandler Out; тем самым говоря — «Out теперь является хэндлером, а не вечно-истинным boolean’ном».А далее в коде в тот момент, когда вы будете готовы продолжить выполнение скрипта, вам нужно вызвать этот хэндлер ровно так же, как было с Finished в предыдущем примере:

if (Out!= null) Out (this, new System.EventArgs ()); Не обязательно писать код самому. Все, что я привел выше — было написано нами, чтобы собрать все, что нужно в одно удобное место. Но это зачастую не обязательно. В uScript есть такая вещь, которая называется «reflection». На деле это означает, что uScript автоматически сканирует вашу сцену и вытягивает из нее все объекты, а так же их публичные методы и параметры, до которых может дотянуться. И предоставляет к ним доступ.Например, вот так выглядит блок-reflection на метод GetComponent () камеры на сцене:

caac139ca823496bae59ef0ab4cf5b13.png

(внизу вы можете видеть блок «properties», где задаются все параметры метода)

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

Но вот для скриптования кат-сцен и диалогов будем юзать однозначно.

Из минусов могу выделить только один (который является следствием плюса) — как я писал выше, uScript преобразует визуальные схемы в C# код. А следовательно каждая модификация схемы потребует перекомпиляции проекта.

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

Кстати, если вам нужна именно для скриптования поведения и взаимодействия объектов на сцене (например, триггеры на столкновения и т.п.), то присмотритесь к PlayMaker. Он больше ориентирован именно на событийную модель.

Все статьи серии:

Идея, вижен, выбор сеттинга, платформы, модели распространения и т.п Шейдеры для стилизации картинки под ЭЛТ/LCD Прикручиваем скриптовый язык к Unity (UniLua) Шейдер для fade in по палитре (а-ля NES) Промежуточный итог (прототип) Поговорим о пиаре инди игр 2D-анимации в Unity («как во флэше») Визуальное скриптование кат-сцен в Unity (uScript)

© Habrahabr.ru