Skip to content

Basic Unit

JasonJonesLASM edited this page Dec 15, 2020 · 2 revisions

Generate a Unit

Getting a simple block generated and onto your graph is pretty easy. We will start by first generating the block, so you can see the process in action each of the ways. As the other tutorials progress, we will assume you know what you learned, and skip showing images for every part.

While these are beginner tutorials, they are not C# tutorials. You will be expected to know most syntax that exists in the language. In this tutorial, you should know how Funcs and Lambdas work, so we can achieve smaller cleaner code.

Lets begin!


First we need to create a C# script. You can create this anywhere in your project that is NOT in an editor folder. Since this unit is only going to be used once to show you how inputs and outputs work, we will simply name it 'BasicUnit'. Assign the script name and class with this name.

Unit is the simplest, and most basic Unit type you can inherit from, so we'll do just that. In order for this unit to have ports, you must override the abstract method called Definition. The method 'Definition' is responsible for defining the ports and other various parts we'll get into later. Define is invoked, which handles othe necessary bits to get this kind of behaviour out of Definition. It is defined, after a hot reload, or when an inspectable field or property is changed on a unit.

In order for the unit to be generated and found, we need to add at least one field. So lets start by defining the variables for inputs and outputs. In this unit, we will add two strings together upon enter.

You'll use a ControlInput and ControlOutput for the flow lines, and ValueInput and ValueOutput for data. The ports need to be public, and should not be serialized. They are redefined each time the unit is reserialized. Therefore you should use the Ludiq Attribute, DoNotSerialize. For this unit, we will need an enter, an exit, 2 string inputs, 1 output, and a private string variable to store the value. With all that said, the code should look like this if you have done it correctly:

using Ludiq;
using Bolt;

public class BasicUnit : Unit
{
    [DoNotSerialize]
    public ControlInput enter;

    [DoNotSerialize]
    public ControlOutput exit;

    [DoNotSerialize]
    public ValueInput A;

    [DoNotSerialize]
    public ValueInput B;

    [DoNotSerialize]
    public ValueOutput result;

    private string resultValue;

    protected override void Definition()
    {

    }
}

Now you should generate the unit for the first time by clicking on the menu item: Tools > Bolt > Build Unit Options.

(DO NOT add custom units into the Unit Options Wizard. It is handled automatically, by reflection. All that will do is add code to the Codebase category. That is probably not what you want.)

After you have generated, you can add your unit to the graph. For this tutorial, you will find it in the root of the fuzzy finder, at the bottom. Once added, you should have this:

First Generation

Control Ports

To define ports you are given 4 methods, named just like the types we have variables for. These are derived from the inherited Unit type. ValueInput, ValueOutput, ControlInput, and ControlOutput.

What you are going to do, is use those in Definition, to assign the port variables. Bolt will do all the magic with those ports itself. You just need to assign them during that method. Not assigning them means they just don't show up. Everytime the unit defines, or senses a change in the unit it, it will attempt to redefine it. That is how logic can effect ports, based on count, toggles, objects, ect.

To assign a Control input, we must give it a string key, and a Func that sends the current flow in, and gets back an output port (the output can also be null). In this case, we want to leave through the 'exit' port. As soon as we enter, we do stuff, and exit.

An exit port is the simplest of all, its just a string key. This is how your code should look now:

using Ludiq;
using Bolt;

public class BasicUnit : Unit
{
    [DoNotSerialize]
    public ControlInput enter;

    [DoNotSerialize]
    public ControlOutput exit;

    [DoNotSerialize]
    public ValueInput A;

    [DoNotSerialize]
    public ValueInput B;

    [DoNotSerialize]
    public ValueOutput result;

    private string resultValue;

    protected override void Definition()
    {
        enter = ControlInput("enter", (flow) => { return exit; });
        
        exit = ControlOutput("exit");
    }
}

Now save it, and your Unit should look like this:

Control Ports

Value Ports

Values are the only other port types to learn about. Thankfully, this is also very simple. We will move on to more complex logic in the future, but for now keep it easy to follow and learn. A value requires a type, via a generic, or a type value. Type values are handy if you want to actually change the type of the port on the fly based on changes on the unit. These are going to be of the type string. There is also the ability to add a default value, which we will do. That would be the 2nd parameter.

using Ludiq;
using Bolt;

public class BasicUnit : Unit
{
    [DoNotSerialize]
    public ControlInput enter;

    [DoNotSerialize]
    public ControlOutput exit;

    [DoNotSerialize]
    public ValueInput A;

    [DoNotSerialize]
    public ValueInput B;

    [DoNotSerialize]
    public ValueOutput result;

    private string resultValue;

    protected override void Definition()
    {
        enter = ControlInput("enter", (flow) => { return exit; });
        
        exit = ControlOutput("exit");

        A = ValueInput<string>("a", string.Empty);
        B = ValueInput<string>("b", string.Empty);
        result = ValueOutput<string>("result");
    }
}

Then you get this:

Value Ports

Finishing Touches

Now that we got this far, our ports are layed out. We just need to combine the two strings into one when we enter. This is super simple. Inside the lambda expression on the assignment of our enter port, we just do some code equivalent to A + B.

In order for us to retrieve the values from A and B, you need to get it from the Flow using GetValue. Thankfully, our method will make sure we have a flow to use from what has entered into it, right there in the lambdas input. Also, in order for us to retrieve a value from the output, lets assign a lambda expression to the result port, and return 'resultValue'. That would be the field that is set on enter.

using Ludiq;
using Bolt;

public class BasicUnit : Unit
{
    [DoNotSerialize]
    public ControlInput enter;

    [DoNotSerialize]
    public ControlOutput exit;

    [DoNotSerialize]
    public ValueInput A;

    [DoNotSerialize]
    public ValueInput B;

    [DoNotSerialize]
    public ValueOutput result;

    private string resultValue;

    protected override void Definition()
    {
        enter = ControlInput("enter", (flow) =>
        { 
            resultValue = flow.GetValue<string>(A) + flow.GetValue<string>(B);
            return exit;
        });
        
        exit = ControlOutput("exit");

        A = ValueInput<string>("a", string.Empty);
        B = ValueInput<string>("b", string.Empty);
        result = ValueOutput<string>("result", (flow) => { return resultValue; });
        
    }
}

This could also be done directly inside the ValueOutput return method, but we want to see how to do Controls as well.

That does it for the unit. If you followed everything correctly, your unit should work like this:

Final

Clone this wiki locally