xNode 是一个用于 Unity 的节点图框架,提供运行时节点、图容器、端口连接和可扩展的节点图编辑器。它适合搭建状态机、对话系统、行为树、数据处理流程和其他自定义图结构。
- 包名:
com.github.siccity.xnode - 版本:
1.8.0 - 最低 Unity 版本:
2022.3 - 运行时程序集:
XNode - 编辑器程序集:
XNodeEditor,仅在 Editor 平台编译 - 依赖:无第三方运行时插件
当前源码以 Unity 2022.3 为最低目标。README 中的旧版 Unity 5.3、2018.3 兼容声明已不再适用。
- 使用
ScriptableObject保存图和节点,支持 Unity 资产序列化。 - 使用
[Input]和[Output]从字段自动生成静态端口。 - 支持多连接、单连接覆盖、动态端口和数组/List 动态端口列表。
- 支持
None、Inherited、Strict、InheritedInverse、InheritedAny五种类型约束。 - 节点图编辑器支持节点创建、复制、删除、拖线、重路由点、框选、网格吸附和自定义节点编辑器。
- 运行时程序集不引用 UnityEditor;编辑器代码通过独立程序集隔离。
- 端口类型和字段反射数据在进程内缓存,避免运行时反复扫描类型。
在项目的 Packages/manifest.json 中加入:
{
"dependencies": {
"com.github.siccity.xnode": "https://github.com/Siccity/xNode.git"
}
}安装 Git URL 依赖需要本机已安装 Git。为了保证构建可复现,正式项目应将 URL 固定到已审核的提交;当前仓库没有发布 tag,不能假设某个 tag 已存在。
openupm add com.github.siccity.xnode也可以将仓库放入项目的 Packages 目录,或直接复制到 Assets 下。使用 Assembly Definition 时:
- 运行时代码引用
XNode。 - 自定义 Editor 代码引用
XNodeEditor。 - 不要让运行时程序集引用
XNodeEditor或 UnityEditor。
节点图继承 NodeGraph,节点继承 Node。节点可以作为图资产的子资产保存。
using XNode;
public class SampleGraph : NodeGraph
{
}
public class AddNode : Node
{
[Input] public float left;
[Input] public float right;
[Output] public float result;
public override object GetValue(NodePort port)
{
if (port == GetOutputPort(nameof(result)))
return GetInputValue<float>(nameof(left), left)
+ GetInputValue<float>(nameof(right), right);
return null;
}
}编辑器中创建 SampleGraph 资产后,可以通过节点图窗口的右键菜单创建 AddNode。也可以通过代码创建节点:
SampleGraph graph = ScriptableObject.CreateInstance<SampleGraph>();
AddNode node = graph.AddNode<AddNode>();
OtherNode otherNode = graph.AddNode<OtherNode>();
node.position = new UnityEngine.Vector2(100, 100);
node.GetOutputPort(nameof(AddNode.result))
.Connect(otherNode.GetInputPort(nameof(OtherNode.input)));
public class OtherNode : Node
{
[Input] public float input;
}推荐从输出端调用 Connect。当前实现也会校验调用方向、端口类型和双方的类型约束,并保持连接记录的双向一致性。
[Input(
backingValue: Node.ShowBackingValue.Unconnected,
connectionType: Node.ConnectionType.Override,
typeConstraint: Node.TypeConstraint.Inherited)]
public ScriptableObject asset;
[Output(
backingValue: Node.ShowBackingValue.Never,
connectionType: Node.ConnectionType.Multiple,
typeConstraint: Node.TypeConstraint.Strict)]
public float value;ConnectionType.Override 表示新连接会替换旧连接,Multiple 表示允许多条连接。类型约束会同时参与编辑器连接过滤和运行时 Connect 校验。
普通动态端口:
NodePort input = node.AddDynamicInput(typeof(float), fieldName: "extraInput");
NodePort output = node.AddDynamicOutput(typeof(float), fieldName: "extraOutput");
node.RemoveDynamicPort(input);数组或 List<T> 可以声明为动态端口列表:
[Input(dynamicPortList = true)]
public float[] inputs;编辑器会为列表元素生成形如 inputs 0、inputs 1 的动态端口,并在增删、重排时同步数组数据。列表元素类型必须能被 Unity 序列化。
节点输出值通过 Node.GetValue(NodePort port) 计算。输入节点可以使用:
GetInputValue<T>(fieldName, fallback):读取第一个有效连接,否则返回 fallback。GetInputValues<T>(fieldName, fallback):读取全部有效连接,否则返回 fallback。NodePort.TryGetInputValue<T>:读取并返回类型匹配状态。NodePort.GetOutputValue():请求输出端口的计算值。
如果图中存在循环连接,求值会被保护逻辑截断并输出警告。节点的 GetValue 应避免依赖副作用,并明确处理输出端口名称不匹配的情况。
可以通过继承 NodeEditor 和 NodeGraphEditor 自定义节点或图的绘制行为:
using XNodeEditor;
[CustomNodeEditor(typeof(AddNode))]
public class AddNodeEditor : NodeEditor
{
public override void OnBodyGUI()
{
serializedObject.Update();
NodeEditorGUILayout.PropertyField(serializedObject.FindProperty("left"));
NodeEditorGUILayout.PropertyField(serializedObject.FindProperty("right"));
serializedObject.ApplyModifiedProperties();
}
}Editor 扩展必须放在 Editor 目录或 Editor-only Assembly Definition 中,并引用 XNodeEditor。复杂连接规则可重写 NodeGraphEditor.CanConnect,但仍建议保持端口自身的类型约束有效。
NodeGraph.nodes保存图内节点引用;节点通常作为图资产的子资产存在。- 连接序列化为对端节点引用、端口字段名和重路由点,运行时端口对象只作为缓存。
- 修改字段名时应使用 Unity 的
[FormerlySerializedAs],便于端口连接迁移。 - 修改端口类型、方向或连接策略后,插件会同步端口定义并清理不再合法的动态列表连接。
- 不要直接修改
NodePort内部连接数据;建立和断开连接应使用Connect、Disconnect、ClearConnections。
测试位于 Tests,测试程序集仅包含 Editor 平台的 EditMode 测试。使用 Unity 2022.3 打开包含本包的项目后,在 Test Runner 中选择 EditMode 运行。
当前测试覆盖节点图复制、节点生命周期、连接双向一致性、Override 连接、类型约束、动态端口、循环求值保护和类型缓存边界。完整 Unity batchmode 验证需要一个实际的 Unity 项目,而本仓库本身是独立包源码,不包含 ProjectSettings。
当前分支以 Unity 2022.3 为最低目标。编辑器代码使用 UnityEditor 的 IMGUI 和 AdvancedDropdown API,并通过版本宏适配部分 Unity 6 API 差异。
1.8.0 包包含连接 API 和端口类型约束相关调整。升级已有项目时,应重新检查自定义节点编辑器、直接调用 Connect 的代码以及自定义类型约束逻辑。
本项目使用 MIT 许可证,详见 LICENSE.md。