Создание игры на ваших глазах — часть 8: Визуальное скриптование кат-сцен в Unity (uScript)
В одной из предыдущих публикаций я рассказывал, что мы прикрутили к нашей игре язык Lua для скриптования различных сценок. Однако, попользовавшись им какое-то время, мы поняли, что порой написание таких скриптов превращается в довольно сложночитаемый и сложноотлаживаемый код.И мы задумались о визуальном подходе. В этой статье я расскажу о нашем знакомстве с средством визуального скриптинга для 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, } В игре это выглядит так: В принципе, в скрипте выше нет ничего страшного. Но представьте, что у вас не 1 ветвение, а два. Представьте, что вам нужно проверять какие-то игровые параметры и ветвить скрипт исходя из них. Это очень быстро может стать ненаглядным.
Именно в такой момент нам остро захотелось визуализации.
Посмотрев несколько плагинов для юнити, мы остановились на uScript. Он очень мощный, гибкий, и при этом просто расширяемый. Кроме того, он создает минимальный impact по быстродействию, т.к. на этапе сохранения схем сразу же компилит их в C#, т.е. для Unity скрипт собранный в таком редакторе не очень отличается от скрипта, написанного руками на шарпах.
Давайте сразу приведу скрин того, во что превратился вышеприведенный LUA-скрипт. (картинка кликабельна)
Выглядит немного громоздко, но зато сразу наглядно. Когда, кто и где создается, что делает, а главное видны ветвления.
Вот, например, в нашем случае игрок может выбрать 1 ответ из двух возможных. В игре это выглядит так:
А на схеме — так:
И сразу видно, что произойдет при выборе ответа №1 и ответа №2. А если таких ветвлений будет больше — то тем более схема не потеряет наглядности.
Принципы uScript. Давайте быстро пробежимся по тому, из чего состоит схема. Собственно, основные модули (в терминологии uScript они называются «nodes») — это событие (с него обычно начинается скрипт или цепочка), action и переменные.
У action’он есть вход (обычно 1) и выход (ы). Например, у самого простого действия 1 вход и 1 выход. А у какого-нить блока условия — уже будет два выхода, например.
Снизу блока подключаются переменные. Треугольник означает, что в переменную будет произведена запись (output).
Например, в этом примере мы создаем персонажа (с помощью блока «Create char»), а потом выставляем ему же зеркальность в «true» (с помощью блока «Mirror»):
Кстати, все переменные могут иметь названия (в нашем случае «с1»). И все переменные одного типа с одинаковым названием будут синхронизированы в пределах одного скрипта (схемы). Т.е. пример выше совершенно идентичен такому:
Сделано это чтобы избавить вас от необходимости тянуть связи через два экрана.
Кроме того, если поставить галочку «expose to Unity», выбранная переменная станет public и будет видна другим скриптам (как визуальным, так и вашим рукописным). Массивы так же поддерживаются.
Немного практики. Все модули, которые вы видите на схеме — самописные. И были написаны за 1 вечер. Давайте посмотрим на их код.Рассмотрим сначала что-нибудь очень простое. Например, action, который называется «Start fight». Он начинает бой (по сути, вызывает метод игровой логики) и принимает два параметра — айдишник боя и айдишник соперника.
Код для него:
[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); } } Просто? Очень.А теперь давайте усложним. Допустим, мы хотим проиграть какую-либо анимацию. И хотим иметь два выхода. Один — сразу, а второй, который запустится только когда анимация проиграется до конца.
Справа вы можете видеть блок с конфигурацией блока, куда вы вбиваете значения. У блока 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 () камеры на сцене:
(внизу вы можете видеть блок «properties», где задаются все параметры метода)
Выводы. Тулза нам однозначно понравилась и мы будем юзать ее дальше. Вообще, некоторые люди умудряются с помощью нее писать целые игры, но это уже через чур.Насколько глубоко мы сможем ее заюзать пока не знаем. Например, еще не решили, переписывать ли логику триггеров квестов с нашей lua-ориентированной на визуальную.
Но вот для скриптования кат-сцен и диалогов будем юзать однозначно.
Из минусов могу выделить только один (который является следствием плюса) — как я писал выше, uScript преобразует визуальные схемы в C# код. А следовательно каждая модификация схемы потребует перекомпиляции проекта.
В остальном — очень советую присмотреться к этому инструменту, если вы хотите скриптовать подобную логику. Так же, насколько я знаю, этот инструмент активно используют для написания AI.
Кстати, если вам нужна именно для скриптования поведения и взаимодействия объектов на сцене (например, триггеры на столкновения и т.п.), то присмотритесь к PlayMaker. Он больше ориентирован именно на событийную модель.
Все статьи серии:
Идея, вижен, выбор сеттинга, платформы, модели распространения и т.п Шейдеры для стилизации картинки под ЭЛТ/LCD Прикручиваем скриптовый язык к Unity (UniLua) Шейдер для fade in по палитре (а-ля NES) Промежуточный итог (прототип) Поговорим о пиаре инди игр 2D-анимации в Unity («как во флэше») Визуальное скриптование кат-сцен в Unity (uScript)