📜 ⬆️ ⬇️

Creating the game on your eyes - part 8: Visual scripting of cut scenes in Unity (uScript)

In one of the previous publications I told you that we screwed the Lua language to our game for scripting various scenes. However, having used it for some time, we realized that sometimes writing such scripts turns into quite difficult to read and hard-to-work code.


And we thought about the visual approach. In this article I will tell about our acquaintance with the means of visual scripting for Unity - " uScript ", about its capabilities and tell you about our experience.

Yes, on the screenshot above - the real script and scheme.

Introduction


So let's take a look at what was before. Below is a real script that creates two characters on the screen, draws a simple dialogue, giving the user a choice of 2 options and branching in this place.
')
LUA script source
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, } 


In the game it looks like this:


In principle, in the script above there is nothing terrible. But imagine that you have not one branch, but two. Imagine that you need to check some game parameters and branch the script based on them. It can very quickly become darling.

It was at this moment that we desperately wanted visualization.

After seeing a few plugins for the unit, we stopped at uScript . It is very powerful, flexible, and at the same time simply expandable. In addition, it creates a minimal impact on speed, because at the stage of saving the schemes, immediately compile them in C #, i.e. for Unity, the script compiled in such an editor is not very different from the script written by hands on sharps.

Let's immediately give a screen of what the above LUA script has become. (the picture is clickable)



It looks a bit cumbersome, but immediately obvious. When, who and where is created, what does, and most importantly visible branches.

Here, for example, in our case, the player can choose 1 answer from two possible. In the game it looks like this:



And on the diagram - like this:



And it is immediately clear what will happen when choosing the answer No. 1 and the answer No. 2. And if there are more such branches, then the more so the scheme will not lose visibility.

Principles of uScript.


Let's quickly go over what the circuit consists of. Actually, the main modules (in the uScript terminology, they are called “nodes”) are an event (the script or a chain usually begins with it), action, and variables.



Action'on has an entrance (usually 1) and an exit (s). For example, the simplest action has 1 input and 1 output. And what thread of the condition block has two outputs, for example.

Variables are connected to the bottom of the block. A triangle means that the output will be written to the variable.

For example, in this example we create a character (using the “Create char” block), and then expose it to mirror “true” (using the “Mirror” block):



By the way, all variables can have names (in our case “c1”). And all variables of the same type with the same name will be synchronized within the same script (scheme). Those. The example above is completely identical to this:



This is done to save you from having to pull connections through two screens.

In addition, if you tick the “expose to Unity” box, the selected variable will become public and will be visible to other scripts (both visual and your handwritten scripts). Arrays are also supported.

A little practice.


All modules that you see on the diagram are self-written. And they were written in 1 evening. Let's look at their code.

Consider first something very simple. For example, action, which is called “Start fight.” He starts the fight (in fact, calls the method of game logic) and takes two parameters - the battle ID and the opponent's IT.



The code for it is:

 [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); } } 

Simply? Highly.

And now let's complicate. Suppose we want to play some kind of animation. And we want to have two choices. One - immediately, and the second, which starts only when the animation is played to the end.



On the right, you can see a block with a block configuration where you drive in values. The block has 3 input parameters - CharacterGfx (the character itself, to which we play the animation), Animation (the name of the animation) and Mirror (the need for mirroring). And the block has two exits: Out (exit right away) and Finished (only when the animation ends).

At the same time, the “Mirror” variable is an enumerator with “yes”, “no” and “no change” parameters, which is represented as a dropdown list in the properties window.

The code is not particularly difficult:

 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()); }; } } 

Another moment. In all blocks above, the output (Out) was called immediately after the execution of the block code.

But what if we want to do an asynchronous action? For example, loading the scene. And so that the execution of our visual script is paused until the scene is asynchronously loaded.

This is done just as easily. Instead of stitching
 public bool Out { get { return true; } } 
which was the flag "the script is always ready for exit", we write:
 public event uScriptEventHandler Out; 
thereby saying, "Out is now a handler, not an ever-true boolean."

And then in the code at the moment when you are ready to continue the execution of the script, you need to call this handler exactly the same way as it was with Finished in the previous example:
 if (Out != null) Out(this, new System.EventArgs()); 

It is not necessary to write the code yourself.


All that I cited above was written by us in order to collect everything that is needed in one convenient place. But this is often not necessary. In uScript, there is such a thing called reflection. In fact, this means that uScript automatically scans your scene and pulls out all the objects from it, as well as their public methods and parameters that can be reached. And provides access to them.

For example, the block reflection on the GetComponent () method of the camera on the stage looks like this:



(below you can see the "properties" block, where all the parameters of the method are set)

Findings.


Tulsa we definitely liked and we will use it further. In general, some people manage to write whole games with it, but this is too much.

How deeply we can zayuzat it yet do not know. For example, they have not yet decided whether to rewrite the logic of quests triggers from our lua-oriented to visual ones.

But here for scripting cut scenes and dialogues we will definitely use it.

Of the minuses, I can select only one (which is a consequence of the plus) - as I wrote above, uScript converts visual schemes into C # code. And consequently each modification of the scheme will require recompilation of the project.

As for the rest, I strongly advise you to take a closer look at this tool, if you want to script such logic. Also, as far as I know, this tool is actively used for writing AI.

By the way, if you need it for scripting the behavior and interaction of objects on the scene (for example, collision triggers, etc.), then look at PlayMaker. He is more focused on the event model.

All articles in the series:
  1. Idea, vision, choice of setting, platform, distribution model, etc.
  2. Shaders for styling images under the CRT / LCD
  3. We fasten the scripting language to Unity (UniLua)
  4. Shader for fade in on the palette (a la NES)
  5. Subtotal (prototype)
  6. Let's talk about the indie games
  7. 2D animations in Unity ("as in flash")
  8. Visual scripting of cut scenes in Unity (uScript)

Source: https://habr.com/ru/post/244493/


All Articles