Skip to content

Wait Units

JasonJonesLASM edited this page Dec 15, 2020 · 2 revisions

Variable Setup Now that you have created your first unit, we are going to teach you how to use coroutines within a unit.

If you need a unit to do logic over time, then this is what you will want to learn. You can theoretically do this without inheriting from WaitUnit, but this already has all we need to do the basics of a Type Writer unit. We will go over how to fill in text at a set interval until it is complete. This unit may be used elsewhere, and actually has a little bit of use in the future. We will be using this in the 3rd tutorial on Relations.

To start, we will setup our variables just like last time. What will we need?


  • A port to start the coroutine. We will use an actual method for this one, instead of a lambda.

  • An exit for right after we start the coroutine.

  • A complete port for when the typing is done.

  • An input value of string for the text we want to write.

  • An input float for the time delay between each character.

  • An output string value port for the current text written.

  • A storing variable for the current text that we will feed out to the current text port.


We won't go over the basics here, so we assume you understand how that can be setup. You should have the following:

using Bolt;
using Ludiq;
using UnityEngine;

public class TypeWriterUnit : Unit
{
    [DoNotSerialize]
    public ControlInput start;

    [DoNotSerialize]
    public ControlOutput exit;

    [DoNotSerialize]
    public ControlOutput typed;

    [DoNotSerialize]
    public ControlOutput complete;

    [DoNotSerialize]
    public ValueInput text;

    [DoNotSerialize]
    public ValueInput delay;

    [DoNotSerialize]
    public ValueOutput current;

    private string storedText = string.Empty;

    protected override void Definition()
    {

    }
}

Defining Ports

Now that we have the setup lets define all the ports before we get to the logic.

When you have a coroutine, the input that runs one, must use an input coroutine instead of a normal input. There is a similar input method like ControlInput, but called ControlInputCoroutine.

This is setup in the exact same way as other inputs, except the 2nd parameter.

Normally we would do: Func<Flow, ControlOutput>

Instead it becomes: Func<Flow, IEnumerator>

Which the IEnumerator is necessary for all Unity Coroutines.

using Bolt;
using Ludiq;
using UnityEngine;

public class TypeWriterUnit : Unit
{
    [DoNotSerialize]
    public ControlInput start;

    [DoNotSerialize]
    public ControlOutput exit;

    [DoNotSerialize]
    public ControlOutput typed;

    [DoNotSerialize]
    public ControlOutput complete;

    [DoNotSerialize]
    public ValueInput text;

    [DoNotSerialize]
    public ValueInput delay;

    [DoNotSerialize]
    public ValueOutput current;

    private string storedText = string.Empty;

    protected override void Definition()
    {
        start = ControlInputCoroutine("start", WriteText);
        text = ValueInput<string>("text", string.Empty);
        delay = ValueInput<string>("delay", 0.2f);
        current = ValueOutput<string>("current", (flow) => { return storedText; });
        typed = ControlOutput("typed");
        complete = ControlOutput("complete");
        exit = ControlOutput("exit");
    }

    private IEnumerator WriteText(Flow flow)
    {
        // Placeholder to prevent errors.
        yield break;
    }
}

Now that we are setup, lets generate the unit. This is what you should have:

Initial Unit

Making the Coroutine

This is the last step and you are on your way. We must create the behaviour for the routine to occur. First we should cache our input values when we first enter the unit. So lets get and set our startingText, time, and count. The count will be used to determine what the current length of the string is. A string is also an array of characters, therefore we will use it to iterate over the characters, by using the current count and counting up.

private IEnumerator WriteText(Flow flow)
{
    var startingText = flow.GetValue<string>(text);
    var _delay = flow.GetValue<float>(delay);
    var count = 1;
}

Next, we know we will need to exit immediately before we ever start looping. A flow itself, is yieldable as an enumerator, so yield the exit port in order to keep the output as a coroutine itself, allowing nestable routines.

private IEnumerator WriteText(Flow flow)
{
    var startingText = flow.GetValue<string>(text);
    var _delay = flow.GetValue<float>(delay);
    var count = 1;
    yield return exit;
}

We're now going to create the most important step, the loop!

Each loop count, we will assign the storedText class variable using SubString. This allows us to grab a section of the full text, based on the current count. From '0', the first character, to 'count', which is the last character. This variable is what we are using to send to the output. That is important.

After that we want to yield our output 'typed' port, wait X amount of seconds, then add another count, and repeat. All this will occur until the storedText is equal or greater then the length of the startingText value. Afterward, we will yield the complete port, and then we are done coding!

private IEnumerator WriteText(Flow flow)
{
    var startingText = flow.GetValue<string>(text);
    var _delay = flow.GetValue<float>(delay);
    var count = 1;


    while (storedText.Length < startingText.Length)
    {
        storedText = startingText.Substring(0, count);

        yield return typed;

        yield return new WaitForSeconds(time);

        count++;
    }

    yield return complete;
}

Lets Test It!

We are all set. Last thing to do is set it up in the graph. We will debug the text to make sure it is working properly. Don't forget, in 1.4 your start event should be checkmarked 'Coroutine' in the graph inspector. Here is what your final result should look like:

Final

Clone this wiki locally