Создание игры на ваших глазах — часть 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)

ссылка на оригинал статьи http://habrahabr.ru/post/244493/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *