Graph Code Generator 是一款将 Rig Graph 使用的绑定逻辑转换为Unity HPC#代码的内置插件。
在 Rig Graph Manager 组件中点击 Generate Graph Code 按钮,即可生成与 Script Graph 逻辑等价的代码。当处于运行时会自动选择对应的代码并执行,可以提升绑定逻辑的执行性能。
考虑如下情况,可以通过将 node 节点进行逻辑与数据的分离,从而得到 node 的数据与 node 的逻辑代码,在执行的时候右边的情况与左边的情况等价。
因此,Rig Graph 产生的所有图逻辑均可转换为与其执行逻辑等价的代码。
Graph Code Generator 受支持的数据结构参见C#/.NET type support。
此外,还有一部分数据结构会被转义成受支持的数据结构,具体如下表:
| 数据结构 | 说明 |
|---|---|
| System.String => Tuanjie.RigGraph.Runtime.RigGraphCore.StringHash | 仅支持 Equal(),GetHashCode(),opeator+,operator= 等操作,最大长度62位 |
| System.Collections.Generic.List=>Tuanjie.RigGraph.Runtime.RigGraphCore.List | 支持IndexOf(),Add(),RemoveAt() 等常见操作不可扩容
|
| UnityEngine.Splines.Spline =>Tuanjie.RigGraph.Runtime.RigGraphCore.NativeSpline | |
| Unity.VisualScripting.Flow=>Tuanjie.RigGraph.Runtime.RigGraphCore.NativeDataBuilder | 仅支持 GetValue(ValueInput v)/ SetValue(ValueOutput o)
|
| Unity.VisualScripting.ValueInput | 仅支持与 NativeDataBuilder 配合使用 |
| Unity.VisualScripting.ValueOutput | 仅支持与 NativeDataBuilder 配合使用 |
| Unity.VisualScripting.ControlInput | 仅空数据结构 |
| Unity.VisualScripting.ControlOutput | 仅空数据结构 |
| 节点说明 | 备注 |
|---|---|
| EventUnit | 事件触发节点,不支持带参数的事件触发 |
| IRigGraphNode | Rig Graph 提供的 custom node |
| GraphInput/GraphOutput/SubGraphUnit | 子图 |
| Literal | 支持的数据结构参考Graph Code Generator转义说明 |
| Get/Set/Call[Reflection function] | 不支持参数中包含不受支持的类型 |
| Sum/Minimum/Maximum/Average/Formula/MergeLists/CreateList | Sum 支持 String类型 |
| If/Loop/Sequence/IfV2 | |
| AddListItem/ClearList/CountItems/GetListItem | |
| Divide/Multiply/Subtract/Distance | |
| GetVariable/SetVariable | |
| And/BinaryComparisonUnit/Or |
using UnityEngine;
using Unity.VisualScripting;
using Unity.Mathematics;
using Unity.Profiling;
using String = System.String;
namespace Tuanjie.RigGraph.Runtime.RigGraphCore
{
[UnitTitle("Set Bone Transform V2")]
[UnitCategory("Custom/Transform")]
[NodeCodeGenerator]
public class SetBoneTransformV2 : Unit, IRigGraphNode
{
[DoNotSerialize]
public ValueInput name;
[DoNotSerialize]
public ValueInput transformSpace;
[DoNotSerialize]
public ValueInput transform;
[DoNotSerialize]
public ValueInput inputPos;
[DoNotSerialize]
public ValueInput inputRot;
[DoNotSerialize]
public ValueInput propagate;
[DoNotSerialize]
public ControlInput inputFlow;
[DoNotSerialize]
public ControlOutput outputFlow;
protected override void Definition()
{
name = ValueInput<String>("Name", "");
transformSpace = ValueInput<Space>("Space", Space.World);
transform = ValueInput<RigidTransform>("Transform", RigidTransform.identity);
inputRot = ValueInput<Quaternion>("Rotation");
inputPos = ValueInput<Vector3>("Position", Vector3.zero);
propagate = ValueInput<bool>("Propagate", true);
inputFlow = ControlInput("Start", (flow) =>
{
SetBoneTransformV2ControlInputFunc(flow, name, transformSpace, transform, inputRot, inputPos, propagate);
return outputFlow;
});
outputFlow = ControlOutput("End");
Succession(inputFlow, outputFlow);
}
[NodeCodeGenerator(nameof(inputFlow))]
public static void SetBoneTransformV2ControlInputFunc(Flow flow, ValueInput name, ValueInput transformSpace, ValueInput transform, ValueInput inputRot, ValueInput inputPos, ValueInput propagate)
{
var p = new ProfilerMarker(nameof(SetBoneTransformV2));
p.Begin();
String m_objName;
Transform1 m_targetTransform;
m_objName = flow.GetValue<String>(name);
m_targetTransform = AnimationStreamQueryFunction.QueryTransform(m_objName, flow);
switch (flow.GetValue<Space>(transformSpace))
{
case Space.World:
{
if (transform.hasValidConnection)
{
RigidTransform m_inputTransform = flow.GetValue<RigidTransform>(transform);
m_targetTransform.rotation = m_inputTransform.rot;
m_targetTransform.position = m_inputTransform.pos;
}
if (inputRot.hasValidConnection)
{
var m_rotation = flow.GetValue<Quaternion>(inputRot);
m_targetTransform.rotation = m_rotation;
}
if (inputPos.hasValidConnection)
{
var m_position = flow.GetValue<Vector3>(inputPos);
m_targetTransform.position = m_position;
}
break;
}
case Space.Self:
{
if (transform.hasValidConnection)
{
RigidTransform m_inputTransform = flow.GetValue<RigidTransform>(transform);
m_targetTransform.localRotation = m_inputTransform.rot;
m_targetTransform.localPosition = m_inputTransform.pos;
}
if (inputRot.hasValidConnection)
{
var m_rotation = flow.GetValue<Quaternion>(inputRot);
m_targetTransform.localRotation = m_rotation;
}
if (inputPos.hasValidConnection)
{
var m_position = flow.GetValue<Vector3>(inputPos);
m_targetTransform.localPosition = m_position;
}
break;
}
}
p.End();
}
}
}
若自定义节点要参与 NodeCodeGenerator 代码生成,需满足以下条件:
[NodeCodeGenerator]特性标记IRigGraphNode接口 [NodeCodeGenerator]
public class SetBoneTransformV2 : Unit, IRigGraphNode
对于 ValueOutput 和 ControlInput 的实现方法,需按以下规则重构为静态函数:
该静态函数需要标记[NodeCodeGenerator(nameof(*))],其中*为对应的 ValueInput 和 ControlOutput 变量名称
方法引用的参数需与调用的 ValueInput/ValueOutput 变量名称相同
方法内部仅限使用受支持的数据类型,不得包含 string 关键字(请使用System.String类型)
[NodeCodeGenerator(nameof(inputFlow))]
public static void SetBoneTransformV2ControlInputFunc(Flow flow, ValueInput name, ValueInput transformSpace, ValueInput transform, ValueInput inputRot, ValueInput inputPos, ValueInput propagate)
如果您无法完整的拆分出函数逻辑,您可以使用CODEGEN_NATIVE宏定义来区分 C# 下的实现与 HPC# 的实现。
[NodeCodeGenerator(nameof(inputFlow))]
#if CODEGEN_NATIVE
//Write HPC# logic in here
#else
//Write C# logic in here
#endif