Advanced
The following is an explanation of the advanced usages in Flow. You can use the following features to improve your workflow while maintaining high performance.
Port Implict Conversation
For reference type objects, such as MonoBehaviour, Component,
ports can be converted based on the inheritance hierarchy automatically.
For example, output port MonoBehaviour can be connected to input port Component.
However, for value type objects, such as int, float, struct, etc and other types that require implicit conversion.
You need to register them manually.
Here is an example that convert custom struct to double:
public class GameplaySetup
{
[RuntimeInitializeOnLoadMethod]
#if UNITY_EDITOR
[UnityEditor.InitializeOnLoadMethod]
#endif
private static unsafe void InitializeOnLoad()
{
CeresPort<SchedulerHandle>.MakeCompatibleTo<double>(handle =>
{
double value = default;
UnsafeUtility.CopyStructureToPtr(ref handle, &value);
return value;
});
CeresPort<double>.MakeCompatibleTo<SchedulerHandle>(d =>
{
SchedulerHandle handle = default;
UnsafeUtility.CopyStructureToPtr(ref d, &handle);
return handle;
});
}
}
Node has Port Array
For nodes that need a resizeable port array for example FlowNode_Sequence,
you can implement IPortArrayNode to define the port array, however, only
one port array is supported for each node type.
public class FlowNode_Sequence : ForwardNode, ISerializationCallbackReceiver, IPortArrayNode
{
// DefaultLength metadata is used to define the default port array length
[OutputPort(false), CeresLabel("Then"), CeresMetadata("DefaultLength = 2")]
public NodePort[] outputs;
[HideInGraphEditor]
public int outputCount;
protected sealed override async UniTask Execute(ExecutionContext executionContext)
{
foreach (var output in outputs)
{
var next = output.GetT<ExecutableNode>();
if(next == null) continue;
await executionContext.Forward(output.GetT<ExecutableNode>());
}
}
public void OnBeforeSerialize()
{
}
public void OnAfterDeserialize()
{
outputs = new NodePort[outputCount];
for (int i = 0; i < outputCount; i++)
{
outputs[i] = new NodePort();
}
}
public int GetPortArrayLength()
{
return outputCount;
}
public string GetPortArrayFieldName()
{
return nameof(outputs);
}
public void SetPortArrayLength(int newLength)
{
outputCount = newLength;
}
}
Generic Node
Generic nodes define type restrictions through template classes, so that argument types can be obtained in the editor and the generic node instance can be constructed at runtime. This helps reduce lines of code.
Following is an implementation example.
[NodeGroup("Utilities")]
[CeresLabel("Cast to {0}")]
[CeresMetadata("style = ConstNode")]
public class FlowNode_CastT<T, TK>: ForwardNode where TK: T
{
[OutputPort(false), CeresLabel("")]
public NodePort exec;
// HideInGraphEditorAttribute is used in input port to restrict
// users to edit fields only by connecting edges
[InputPort, HideInGraphEditor, CeresLabel("Source")]
public CeresPort<T> sourceValue;
[OutputPort, CeresLabel("Cast Failed")]
public NodePort castFailed;
[OutputPort, CeresLabel("Result")]
public CeresPort<TK> resultValue;
protected sealed override UniTask Execute(ExecutionContext executionContext)
{
try
{
resultValue.Value = (TK)sourceValue.Value;
executionContext.SetNext(exec.GetT<ExecutableNode>());
}
catch (InvalidCastException)
{
executionContext.SetNext(castFailed.GetT<ExecutableNode>());
}
return UniTask.CompletedTask;
}
}
Then define a class named as {node name}_Template implementing IGenericNodeTemplate or
derived from GenericNodeTemplate.
public class FlowNode_CastT_Template: GenericNodeTemplate
{
// Notify editor FlowNode_CastT need user to drag a port
public override bool RequirePort()
{
return true;
}
public override Type[] GetGenericArguments(Type portValueType, Type selectArgumentType)
{
return new[] { portValueType, selectArgumentType };
}
public override Type[] GetAvailableArgumentTypes(Type portValueType)
{
return CeresPort.GetAssignedPortValueTypes()
.Where(x => x.IsAssignableTo(portValueType) && x != portValueType)
.ToArray();
}
protected override string GetGenericNodeBaseName(string label, Type[] argumentTypes)
{
/* Cast to {selectArgumentType} */
return string.Format(label, argumentTypes[1].Name);
}
}
Custom Function
Local Function
You can define local function inside your flow graph to reuse logic.
You can create a local function by following these steps:
Click blackboard
+button and selectFunctionin menu which will let you open subGraph view.Configure the function input and output parameters.

Save the local function subGraph.
Enter uber graph and drag the function from blackboard to graph.

You can modify the name of local function just like modifing a variable.
Flow Graph Function
You can define a shared function across multiple graph containers using FlowGraphFunctionAsset.
You can create a flow graph function by following these steps:
- Right click project browser and select
Create/Ceres/Flow Graph Functionto create a newFlowGraphFunctionAsset. - Configure the function input and output parameters.
- Save the flow graph.
- Rename
FlowGraphFunctionAssetasset name which will also be the function name. - Set your flow graph function runtime type in inspector.

- Open another flow graph.
- Select your flow graph function by its asset name in search window.

Type Preservation
Ceres provides an automatic type preservation system that prevents IL2CPP code stripping from removing types used in your visual scripts.
How It Works
CeresLinker will cache types used in nodes during your development and identifies all types that need to be preserved. It will generate a temporal link.xml file in build time to prevent IL2CPP from stripping these essential types.
Manual Type Registration
You can manually register additional types that should be preserved using the CeresLinker API:
using Ceres.Editor;
// Register a single type
CeresLinker.LinkType(typeof(MyCustomClass));
// Register multiple types
CeresLinker.LinkTypes(new Type[] {
typeof(MyCustomClass),
typeof(AnotherClass)
});
// Save the registered types to settings
CeresLinker.Save();
Editor Integration
You can view and manage preserved types through Unity's Project Settings:
- Go to
Edit > Project Settings > Ceres - Check the "Preserved Types" section to see all registered types
- Manually add type names if needed
Next Steps
- Learn about Custom Nodes for creating reusable node logic
- Explore Generic Nodes for type-safe generic operations
- Check Port Implicit Conversion for custom type conversions
- See Custom Functions for local and shared functions