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

    In a previous publication , I said that we tightened our game Lua scripting language for various scenes. However, after using it for some time, we realized that sometimes the writing of such scripts turns into quite difficult to read and difficult to debug code.


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

    Yes, in the screenshot above - a real script and diagram.

    Introduction


    So, let's start by looking at what happened. The following 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 to worry about. But imagine that you have not 1 branch, but two. Imagine that you need to check some game parameters and branch the script based on them. It can become darling very quickly.

    It was at such a moment that we really wanted visualization.

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

    Let’s immediately give a screen shot of what the above LUA script has turned into. (the picture is clickable) It looks a bit bulky, but immediately obvious. When, who and where is created, what does, and most importantly, the branches are visible. 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 you can immediately see what happens when you select answer No. 1 and answer No. 2. And if there are more such branches, then all the more the scheme will not lose its visibility.















    The principles of uScript.


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



    An action has an input (usually 1) and an output (s). For example, the simplest action has 1 input and 1 output. And a thread of a condition block will already have two outputs, for example.

    At the bottom of the block, variables are connected. A triangle means that a variable will be written to (output).

    For example, in this example, we create a character (using the “Create char” block), and then set it to mirror as “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 (circuit). Those. the example above is completely identical to this: This is



    done to save you from having to pull the connection through two screens.

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

    A bit of practice.


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

    First, consider something very simple. For example, an action called “Start fight”. He starts the battle (in fact, calls the method of game logic) and takes two parameters - the combat identifier and the opponent's identifier.



    Code for it:

    [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.

    Now let's complicate it. Let's say we want to play some kind of animation. And we want to have two exits. One - immediately, and the second, which will only start when the animation is played to the end.



    On the right you can see the block with the configuration of the block where you drive the values. The block has 3 input parameters - CharacterGfx (directly the character to whom we play the animation), Animation (the name of the animation) and Mirror (the need for mirroring). And the block has two outputs: Out (exit immediately) and Finished (only when the animation is finished).

    The variable “Mirror” is an enumerator with the parameters “yes”, “no” and “do not change”, which is presented as a dropdown list in the properties window.

    The code is not particularly complicated:

    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 block code was executed.

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

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

    And then in the code at the moment when you are ready to continue executing the script, you need to call this handler exactly the same 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 to collect everything you need in one convenient place. But this is often not necessary. There is such a thing in uScript 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 they can reach. And provides access to them.

    For example, this is how the reflection block looks at the GetComponent () method of the camera on the scene:



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

    Conclusions.


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

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

    But for scripting cut scenes and dialogs we will use it unambiguously.

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

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

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

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

    Also popular now: