Создание игры на ваших глазах — часть 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. Он больше ориентирован именно на событийную модель.

    Все статьи серии:
    1. Идея, вижен, выбор сеттинга, платформы, модели распространения и т.п
    2. Шейдеры для стилизации картинки под ЭЛТ/LCD
    3. Прикручиваем скриптовый язык к Unity (UniLua)
    4. Шейдер для fade in по палитре (а-ля NES)
    5. Промежуточный итог (прототип)
    6. Поговорим о пиаре инди игр
    7. 2D-анимации в Unity («как во флэше»)
    8. Визуальное скриптование кат-сцен в Unity (uScript)
    Поделиться публикацией
    Комментарии 16
      +1
      Спасибо за статью.
      Сам uScript не юзал, не было возможности. Как для меня он немного деревянно выглядит (визуально), возможно потому, что я привык к Unreal Blueprints.
      Я решил написать свой плагин по визуальному программированию в юнити по типу Unreal Blueprints.

      На данный момент плагин закончен на 90% и содержит в себе:
      Функций: 3993
      Типов переменных: 428
      Энумераторов: 125

      В нем только функции и переменные самого юнити. еще нужно добавить с IO, Collections и т. д.
      Над оптимизацией еще нужно будет поработать.
      Скрин.
      Видео. Тут показан визуальный реалтайм дебаг скрипта.
      Свой плагин пока никому не показывал.

      Эта тема для меня очень интересна, по этому хотелось бы обсудить.
        0
        Выглядит нереально круто, конечно! Но вот, например, мы бы такую штуку не взяли, для нас это слишком громоздко и сложно. По этой причине не взяли antares universe. Вы, кстати, его смотрели? Он, вроде, еще более функциональный нежели uScript.
          0
          Посмотрел. Судя по видео тоже самое что и uScript. (принцип тот же)
          0
          А можно в нескольких словах, какая принципиальная разница между Unreal Blueprints и uScript?
        –1
        Т.е. вас не устроил стоковый скриптинг Unity3D (C#), вы прикрутили свой на lua. И он тоже вас не устроил, ибо со временем кода получилось много, и посему вы решили прикрутить визуалку.Интересно вот что: когда у вас схемы перестанут помещаться в экран вы тоже что-то поверх налепите?
          0
          Не понимаю причин вашего сарказма.

          Стоковый скриптинг не подходит. Скриптование квестов и т.п. — задача гейм-дизайнера. Гейм-дизайнер не должен лазить в код. Редактировать исходники игры для добавления нового квеста — однозначно неверное решение.

          А то, что схемы не влезают в экран — тоже не проблема. Есть скроллинг и зуминг.
            –1
            >>А то, что схемы не влезают в экран — тоже не проблема. Есть скроллинг и зуминг.

            Скроллинг и зуминг вас почему-то не устроил при использовании lua. Я просто лишь продолжил мысль.
              0
              Нас не устроил не объем кода, а отсутствие наглядности и легкочитаемости.
                –1
                В этом и была суть первого комментария — когда пропадёт наглядность и «легкочитаемость» блок-схем, поверх них тоже будет что-либо водружено?
                  0
                  Небольшой некропостинг, но меня заинтересовал тот же вопрос товарища 6opoDuJIo. С момента написания статьи прошло достаточно много времени, поэтому хотелось бы узнать насколько оправдал себя uScript с точки зрения представленных преимуществ? История с переходом Lua не повторилась (не перестали ли умещаться схемы на экран)?
                0
                Редактировать исходники игры для добавления нового квеста — однозначно неверное решение.
                Так у вас же и так при добавлении нового квеста происходит перекомпиляция проекта. Но с тезисом согласен: гейм-дазайнер не должен лазить в код.
                  0
                  Что там происходит за пределами диаграммы, не суть важно. Важно именно то, что у ГД в руках инструмент со схемами и стрелочками и кодить он не лезет ;)
                    0
                    А итоговые код оно генерирует оптимальный? Или оверхед слишком мал, чтобы об этом задумываться?
                      0
                      Код… Скажем так, весьма обфусцированный =) Но с первого взгляда не очень громоздкий.

                      А вообще — тут совсем не имеет значения, ведь речь идет о скриптинге ивентов, а не об апдейте, который крутится каждый фрейм для каждого из 10 тыс. объектов. Так что оверхед тут абсолютно не важен.
                        +1
                        Благодарю, может тоже присмотримся к инструменту. Для кат-сцен самое оно.

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

            Самое читаемое