You’ll have to poke around a bit to find a great way to do this. It sounds like you’re up for coding a Bolt Unit, but that isn’t generally well documented.
You can do this though.
The way to code a unit is to extend from the “Unit” class.
On that Unit, you can add fields of one of four types
ValueInput
ValueOutput
ControlInput
ControlOutput
These generally require the attribute [DoNotSerialize]
, since the ports aren’t actually something you want to store and Bolt uses a different serialization system - they are generated by the “Define” method, and may change based upon data you actually serialize.
Speaking of which, you then have to override this protected method called “Define” and then you can implement your unit by defining the ports, their callbacks, and optionally their relationships. You assign each of the fields you’ve created. You may be tempted to use new
and do something like
this.character= new ValueInput("character") // WRONG
but the Unit class provides methods you should use instead
this.character = ValueInput("character")
The ControlInput method has a Coroutine version that you should use.
If you want your unit to highlight and warn of missing ports correctly, you should use the Succession(), Requirement, and Assigns() methods. Succession says that a control input will invoke a control output. Requirement says that a control output or value output requires a value input. And finally, Assigns says that invoking a control input will assign a value on a value output. (As an aside, values can be assigned using flow.SetValue
, or they can invoke a method to retrieve their value. You don’t need any control ports on your custom unit.)
Here’s some code that might get you started, or - dare I say - finished? Either way it should be a good example if you approach other situations like this.

using System.Collections;
using Unity.VisualScripting;
using UnityEngine;
public class ExampleUnit : Unit
{
[DoNotSerialize]
public ValueInput character { get; private set; }
[DoNotSerialize]
public ValueInput destination { get; private set; }
[DoNotSerialize]
[PortLabelHidden]
public ControlInput enter { get; private set; }
[DoNotSerialize]
[PortLabelHidden]
public ControlOutput exit { get; private set; }
protected override void Definition()
{
enter = ControlInputCoroutine("enter", RunCoroutine);
exit = ControlOutput("exit");
character = ValueInput<Character>("character", null);
character.NullMeansSelf();
destination = ValueInput<Vector2>("destination", Vector2.zero);
}
private IEnumerator RunCoroutine(Flow flow)
{
var characterValue = flow.GetValue<Character>(character);
var destinationValue = flow.GetValue<Vector2>(destination);
bool complete = false;
characterValue.MoveTo(destinationValue.x,
destinationValue.y,
() => complete = true);
// you might have to add some more logic to
// detect if the character is destroyed or any special cases
yield return new WaitUntil(() => complete);
yield return exit;
}
}
You’ll have to rebuild all bolt units after doing this.
It might be a good next step for you to figure out how to earn your own bonus points. Self-care and all that.
(If you’re looking for a variable number of outputs, you can define an int field marked with [Serialize] or [SerializeAs] to store it, and use it during the Define to fill in a List<ControlOutput>
field.)
Other more general approaches could include coding a unit that returns an Action
that uses Flow.New
when invoked, but that can get hairy (certain variables and value ports will be out of scope since they weren’t assigned on the same flow), or to create a unit for running a general coroutine from an IEnumerator
valueInput.