- Install
- Highlights
- Platforms
- Core Concepts
- Quick Start Guide
- Flow Visual Scripting
- ExecutableReflection
- Debugging
- Advanced Features
- Type Preservation
- Code Generation
- Performance
- Documentation
- Implementation
- Articles
- License
Add following dependencies to manifest.json.
"dependencies": {
"com.kurisu.chris": "https://github.com/AkiKurisu/Chris.git",
"com.cysharp.unitask":"https://github.com/Cysharp/UniTask.git?path=src/UniTask/Assets/Plugins/UniTask"
}Use git URL to download package by Unity Package Manager https://github.com/AkiKurisu/Ceres.git.
Powerful visual scripting toolkit that brings the best of both worlds - visual programming and C# code integration.
- 🎯 Generic & Delegate Support - Full support for C# generics and delegates in visual scripting
- 🔗 Graph & C# Integration - Seamless integration between visual graphs and traditional C# code
- 🐛 Editor Debugging - Built-in debugging tools with breakpoints and step-by-step execution
- ⚡ Easy Implementation - Simple setup with minimal configuration required
- 🏃♂️ Optimized Runtime Performance - High-performance execution with IL2CPP optimizations
- 📱 IL2CPP Compatible - Fully compatible with IL2CPP builds and recommended for production
- Intuitive Node-Based Editor - Clean, modern interface for creating visual scripts
- Real-time Preview - See your logic flow as you build it
- Professional Workflow - Industry-standard tools and practices
Unity 2022.3 LTS or later, compatible with Unity 6.
Understanding the fundamental concepts of Ceres will help you get started quickly.
CeresNode is the logic and data container that forms the building blocks of your visual scripts.
CeresPort enables you to get data from other nodes. Ceres uses CeresPort<T> to get generic data from other nodes and NodePort to get a NodeReference which can convert to CeresNode at runtime.
CeresGraph contains everything and acts as a virtual machine that executes your visual scripts.
Ceres serializes node, port and graph to additional data structures: CeresNodeData, CeresPortData and CeresGraphData which contain the actual serialized data and metadata.
SharedVariable is a data container that can be shared between nodes and graphs. Compared with CeresPort, SharedVariable can be edited outside of the graph and doesn't contain any connection data since it doesn't need to know where the data comes from.
Nodes can be executed in two ways:
Forward: Graph can execute nodes one by one.
Dependency: Graph should execute node's dependencies before executing the node.
As shown in the figure, to execute Log String, we need to first get the variable message. However, since Get message has no outer connection, it has not been executed before. So the graph needs to consider Get message as a dependency and execute it before executing Log String.
Here's a step-by-step example of using Flow to output a "Hello World" message:
Create a new C# script MyFlowObject.cs and make it inherit from FlowGraphObject:
using Ceres.Graph.Flow;
using Ceres.Graph.Flow.Annotations;
public class MyFlowObject: FlowGraphObject
{
[ImplementableEvent]
private void Start()
{
}
}Create a new GameObject in the scene and attach the MyFlowObject component to it.
Click Open Flow Graph in the Inspector panel to open the Flow Graph Editor.
Right-click the graph and click Create Node/Select Events/Implement Start.
Click Create Node and search Log String, then connect the white port (exec) to the Start node's output (exec). Fill in "Hello World!" in the In String field of the Log String node.
Click the save button in the left upper corner, then play the game. You will see "Hello World!" in the console.
Flow thinks of game logic as an execution chain to let game objects do things in order according to your design. Flow visualizes these executions as nodes so that you can connect them to implement gameplay and functionality.
Each execution chain starts from an event which can contain input data.
You can define events in Flow Graph or C# scripts.
Functions are components that implement game functions. They complete specified functions by calling the engine or your custom API. Through wiring, you can combine these method calls to complete your creativity.
For any FlowGraph instance, there needs to be a specified Container at runtime. It can be your custom MonoBehaviour (need to implement interface) or inherit from a series of parent classes provided by Ceres.
ExecutableReflection allows you to expose C# methods to Flow graphs, making them available as nodes. There are three main ways to define executable functions:
For instance methods, add ExecutableFunctionAttribute directly to your method:
using Ceres.Graph.Flow.Annotations;
using UnityEngine;
public class MyComponent : MonoBehaviour
{
[ExecutableFunction]
public void DoSomething(int arg1, float arg2)
{
Debug.Log($"Doing something with {arg1} and {arg2}");
}
[ExecutableFunction]
public string GetPlayerName()
{
return "Player";
}
}For static methods, create a partial class that inherits from ExecutableFunctionLibrary and add ExecutableFunctionAttribute:
using Ceres.Graph.Flow;
using Ceres.Graph.Flow.Annotations;
using Ceres.Annotations;
using UnityEngine;
public partial class GameplayFunctionLibrary : ExecutableFunctionLibrary
{
[ExecutableFunction(IsScriptMethod = true, IsSelfTarget = true)]
[CeresLabel("Get GameObject Name")]
public static string Flow_GetGameObjectName(GameObject gameObject)
{
return gameObject.name;
}
[ExecutableFunction(ExecuteInDependency = true)]
public static float Flow_CalculateDistance(Vector3 pointA, Vector3 pointB)
{
return Vector3.Distance(pointA, pointB);
}
[ExecutableFunction]
public static Component Flow_FindComponent(
GameObject target,
[ResolveReturn] SerializedType<Component> componentType)
{
return target.GetComponent(componentType);
}
}Important: You must add the
partialmodifier to let the source generator work. The source generator registers static function pointers to enhance runtime performance instead of using MethodInfo.
Flow automatically includes assemblies matched by Always Included Assembly Wildcards in Project Settings/Ceres/Flow Settings. Methods in these assemblies can be invoked without adding attributes:
public class UtilityClass
{
public void AutomaticallyIncludedMethod(string message)
{
Debug.Log($"Auto-included: {message}");
}
}-
Unique Method Names: Methods with the same name and parameter count in the same class hierarchy can only have one
ExecutableFunctionAttribute. -
Method Overloads: For methods with the same name but different parameters, use
CeresLabelAttributeto distinguish them:
[ExecutableFunction, CeresLabel("Log Message")]
public static void Flow_Log(string message)
{
Debug.Log(message);
}
[ExecutableFunction, CeresLabel("Log Message with Color")]
public static void Flow_Log(string message, Color color)
{
Debug.Log($"<color=#{ColorUtility.ToHtmlStringRGB(color)}>{message}</color>");
}-
Parameter Limits: Keep input parameters ≤ 6 for optimal editor performance. Methods with more parameters use Uber nodes with greater runtime overhead.
-
Generic Methods: Generic methods are not supported with
ExecutableFunctionAttribute. Use Generic Nodes instead.
Ceres provides powerful debugging capabilities to help you troubleshoot your visual scripts.
To enable and disable debug mode, click the debug button in the upper right corner. Then, you can click Next Frame to execute the graph node by node.
You can right click any node and select Add Breakpoint, then click Next Breakpoint in the toolbar to execute the graph breakpoint by breakpoint.
Ceres editor can display the current value of an input port when the mouse hovers over the port of the node at the current breakpoint.
FlowGraphTracker is a class that can be used to track the execution of the graph for advanced debugging scenarios.
For reference type objects, such as MonoBehaviour and Component, ports can be converted based on the inheritance hierarchy automatically. For value types, you need to register conversions manually.
Generic nodes define type restrictions through template classes, allowing argument types to be obtained in the editor and generic node instances to be constructed at runtime.
You can define both local functions within your flow graph and shared functions across multiple graph containers using FlowGraphFunctionAsset.
For nodes that need resizable port arrays, you can implement IPortArrayNode to define dynamic port collections.
Ceres provides an automatic type preservation system that prevents IL2CPP code stripping from removing types used in your visual scripts.
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.
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();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
Ceres uses multiple code generation techniques to enhance runtime performance:
Analyzes partial classes annotated with GenerateFlowAttribute and generates their implementation automatically, reducing code duplication.
Uses ILPP to emit IL for initialization logic of CeresNode and methods annotated with ImplementableEventAttribute to enhance runtime performance.
Ceres provides a variety of performance enhancements to improve the runtime performance of your visual scripts.
Relay nodes are completely flattened during serialization. At runtime, connections bypass relay nodes entirely, connecting source nodes directly to target nodes.
The picture above shows relay nodes do not effect execution flow (only forward execution three times for Start, Find GameObject, and Log String).
Ceres implements sophisticated IL2CPP optimizations to achieve near-native performance for executable function calls. The optimization strategy varies by platform to maximize performance while maintaining compatibility.
Windows & Android Platforms For Windows and Android builds, Ceres directly utilizes IL2CPP's native API to bypass reflection overhead entirely:
#if ENABLE_IL2CPP && (UNITY_STANDALONE_WIN || UNITY_ANDROID)
unsafe
{
if (functionType == ExecutableFunctionType.InstanceMethod && _il2cppClass != IntPtr.Zero)
{
// Direct IL2CPP method lookup and function pointer invocation
var ptr = IL2CPP.GetIl2CppMethod(_il2cppClass, $"Invoke_{functionName}", invokeParameterCount);
if (ptr != IntPtr.Zero)
{
functionStructure = new ExecutableFunction(functionInfo, ptr, false);
_functions.Add(functionStructure);
return functionStructure;
}
}
}
#endifThis approach provides the highest performance by:
- Direct Function Pointer Calls: Using
delegate* unmanaged[Cdecl]function pointers for zero-overhead method invocation - Native IL2CPP API: Leveraging
il2cpp_class_get_method_from_nameandil2cpp_method_get_function_pointerfor direct method resolution - Bypassing Reflection: Eliminating
MethodInfooverhead completely at runtime
Other Platforms For platforms where direct IL2CPP API access is limited, Ceres uses IL Post Processing (ILPP) to inject static bridge functions:
#if ENABLE_IL2CPP
// We haven't injected IL in always included assembly, use legacy way in this case.
if (!FlowConfig.IsIncludedAssembly(targetType.Assembly))
{
targetType.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.FlattenHierarchy)
.Where(x => x.GetCustomAttribute<ExecutableFunctionAttribute>() != null)
.ToList()
.ForEach(RegisterExecutableFunctionInvoker);
return;
}
#endifThe ILPP approach provides significant performance improvements by:
- Static Bridge Functions: Injecting
Invoke_prefixed static methods that wrap executable functions - Function Pointer Registration: Registering native function pointers during build time
- Delegate Optimization: Using
delegate* unmanaged[Cdecl]calling convention for minimal overhead
For more detailed information, you can also visit:
- Online Tutorial
- API Reference
- Ceres Wiki generated by DeepWiki
Ceres built-in event-driven visual scripting solution.
See Startup Flow
Implement a Data-Driven and Per-Actor hotupdate solution.
See Chris.Gameplay for more details.
AI powered dialogue visual designer for Unity.
See Next-Gen-Dialogue.
Technique articles related to Ceres.
MIT












