Version: 1.7
语言 : 中文
Rig Graph 资产构建
附录

Graph Code Generator 概述

Graph Code Generator 是一款将 Rig Graph 使用的绑定逻辑转换为Unity HPC#代码的内置插件。

在 Rig Graph Manager 组件中点击 Generate Graph Code 按钮,即可生成与 Script Graph 逻辑等价的代码。当处于运行时会自动选择对应的代码并执行,可以提升绑定逻辑的执行性能。

Script Graph执行绑定逻辑Profile
Script Graph执行绑定逻辑Profile
生成代码执行绑定逻辑Profile
生成代码执行绑定逻辑Profile

概述

考虑如下情况,可以通过将 node 节点进行逻辑与数据的分离,从而得到 node 的数据与 node 的逻辑代码,在执行的时候右边的情况与左边的情况等价。

因此,Rig Graph 产生的所有图逻辑均可转换为与其执行逻辑等价的代码。

Graph Code Generator 转义说明

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 仅空数据结构

Graph Code Generator受支持的节点

节点说明 备注
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

Graph Code Generator 当中 Custom Node 的案例

以下内容摘自 SetBoneTransformV2.cs。

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 代码生成,需满足以下条件:

  1. 添加[NodeCodeGenerator]特性标记
  2. 继承IRigGraphNode接口
  3. 文件名与类名保持一致
    [NodeCodeGenerator]
    public class SetBoneTransformV2 : Unit, IRigGraphNode

对于 ValueOutput 和 ControlInput 的实现方法,需按以下规则重构为静态函数:

  1. 该静态函数需要标记[NodeCodeGenerator(nameof(*))],其中*为对应的 ValueInput 和 ControlOutput 变量名称

  2. 方法引用的参数需与调用的 ValueInput/ValueOutput 变量名称相同

  3. 方法内部仅限使用受支持的数据类型,不得包含 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
Rig Graph 资产构建
附录