ControlRig_C++_自定义节点内调用其他节点模板
【ControlRig C++】在自定义节点内部调用其他需要 ExecuteContext的节点
目标:在自定义
FRigUnit的_Execute()里,调用另一个 ControlRig 节点(如FRigUnit_SpringInterpQuaternionV2)
省流版
对于需要 ExecuteContext的节点,只需要把它存成自身结构体的成员,只改你需要的字段,然后 Unit.Execute(ExecuteContext)。
状态跨帧持久、默认值由构造函数填好、参数最少。
推荐模板:实例方式
.h
// 必须包含被调用节点的头 —— 否则 FQuaternionSpringState 等类型不完整
#include "Units/Simulation/RigUnit_SpringInterp.h"
USTRUCT(meta = (DisplayName = "MyNode"), Category = "MyCat")
struct HYBRIDANIM_API FRigUnit_MyNode : public FRigUnit
{
GENERATED_BODY()
RIGVM_METHOD()
virtual void Execute() override;
// 你的输入 / 输出引脚
UPROPERTY(meta=(Input))
FTransform InputTransform;
UPROPERTY(meta=(Output))
FTransform Result;
// 被调用的内部节点:作为【成员】保存
// 构造函数已把它的所有字段填好默认值,无需手填。
UPROPERTY()
FRigUnit_SpringInterpQuaternionV2 SpringUnit;
//内部节点用到的参数
UPROPERTY()
float SpringStrength = 4.0f;
};
.cpp
FRigUnit_MyNode_Execute()
{
URigHierarchy* Hierarchy = ExecuteContext.Hierarchy;
if (!Hierarchy) return;
// 1) 准备好要传给内部节点的目标值
FQuat TargetQuat = /* 你的计算 */;
// 2) 只设需要的字段,其余保持构造函数默认值
SpringUnit.Target = TargetQuat;
SpringUnit.Strength = SpringStrength;
// 3) 执行该节点
SpringUnit.Execute(ExecuteContext);
// 4) 取输出、组装你的 Result
Result = InputTransform;
Result.SetRotation(SpringUnit.Result * /* ... */);
}
备选模板:StaticExecute 方式(显式,参数多)
不想把整个内部节点存成成员、想一眼看清所有输入输出时用。
代价:必须填满全部参数,无默认值,且状态成员要单独存。
.h
// 单独存状态成员(不暴露成引脚,但要 UPROPERTY 以便序列化/重置)
UPROPERTY() FQuaternionSpringState SpringState;
UPROPERTY() FQuat SimulatedResult;
.cpp
FQuat OutResult;
FVector OutAngVel;
FRigUnit_SpringInterpQuaternionV2::StaticExecute(
ExecuteContext,
TargetQuat, SpringStrength, 1.0f, // Target, Strength, CriticalDamping
FVector::ZeroVector, false, FQuat::Identity, // Torque, bUseCurrentInput, Current
0.0f, false, // TargetVelocityAmount, bInitializeFromTarget
OutResult, OutAngVel, SimulatedResult, SpringState); // 输出 + 持久状态(按引用)
关键规则(踩过的坑)
| # | 规则 | 原因 |
|---|---|---|
| 1 | 内部节点 + 它的状态必须是你结构体的成员,绝不能是函数局部变量 | 弹簧 / 累加类节点靠跨帧状态工作;局部变量每帧重建 → 状态清零 → 弹簧废 |
| 2 | .h 要 #include 内部节点的头 |
否则类型不完整(FQuaternionSpringState 等)报"不完整类型" |
| 3 | 状态成员用UPROPERTY() 但不加 meta=(Input/Output) |
要序列化 / 重置,但不暴露成图表引脚 |
| 4 | ExecuteContext 在 _Execute() 宏体里自动可见,直接用 |
宏展开成StaticExecute(const FRigVMExecuteContext& ExecuteContext, ...) |
| 5 | 调实例Unit.Execute(ExecuteContext),不要手动 Unit.Execute() 空参 |
空参版是占位;带 context 的版本才是 UHT 生成、能拿到 DeltaTime 的真入口 |
| 6 | intellisense 报ExecuteContext "无法解析符号" 是误报 |
它不展开RIGVM_METHOD 宏;能正常编译,忽略即可 |
为什么 Execute(ExecuteContext) 能用(原理速记)
FRigUnit_SpringInterpQuaternionV2这类 sim 节点,其真正的 C++ 逻辑写在
FRigUnit_SpringInterpQuaternionV2_Execute() { ... }宏体里。- 该宏展开成
StaticExecute(const FRigVMExecuteContext& ExecuteContext, const FQuat& Target, ...)
的函数体,内部直接用ExecuteContext.GetDeltaTime()、Target、SpringState等形参。 - UHT 另外为每个 unit 生成实例方法
Execute(const FRigVMExecuteContext& InExecuteContext),
它把this->各成员喂给StaticExecute并写回成员 —— 与 VM 运行节点等价。 - 所以实例
Unit.Execute(ctx)= 读成员 → 算 → 写回成员。
引擎先例:AdditiveControlRig.cpp:60 自己就是这么调的:
Unit.Transform = GetHierarchy()->GetLocalTransform(Index);
Unit.Execute(ExecuteContext);
两种方式怎么选
| 实例方式(推荐) | StaticExecute 方式 | |
|---|---|---|
| 参数量 | 只改要用的 | 全部填满,无默认值 |
| 状态持久 | 整个Unit 当成员,天然持久 |
单独存SpringState/SimulatedResult 并按引用传 |
| 显式度 | 低(依赖"Execute 读写成员"约定) | 高(所有输入输出一眼可见) |
| 适用 | 大多数场景,默认选这个 | 想显式控制、或不想把整个节点存成员时 |
可调用范围
任何带 RIGVM_METHOD 的 FRigUnit / FRigVMFunction_SimBase 派生 USTRUCT 都能这么调:
FRigUnit_SpringInterp*、FRigUnit_SetCurveValue、FRigUnit_GetTransform……
真实示例(HybridAnim 项目)
.h — FRigUnit_HeadRotationClamp
#pragma region HeadRotation限制
//HeadRotation限制
USTRUCT(meta = (DisplayName = "HeadRotationClamp"), Category = "LeftRightLook")
struct HYBRIDANIM_API FRigUnit_HeadRotationClamp : public FRigUnit
{
GENERATED_BODY()
RIGVM_METHOD()
virtual void Execute() override;
UPROPERTY(meta=(Input))
FTransform HeadAfterAim_Initial;
UPROPERTY(meta=(Output))
FTransform Result;
UPROPERTY()
FRigUnit_SpringInterpQuaternionV2 SpringUnit;
UPROPERTY()
float SpringStrength = 2.0f;
};
#pragma endregion
.cpp — FRigUnit_HeadRotationClamp_Execute()
#pragma region HeadRotation限制
//HeadRotation限制
FRigUnit_HeadRotationClamp_Execute()
{
URigHierarchy* Hierarchy = ExecuteContext.Hierarchy;
if(!Hierarchy)
{
return;
}
FRigElementKey HeadRig = FRigElementKey(TEXT("head"), ERigElementType::Bone);
FTransform HeadTransform = Hierarchy->GetGlobalTransform(HeadRig);
//头部Aim旋转量
FQuat HeadAimRotateAmount = HeadAfterAim_Initial.GetRotation() * HeadTransform.GetRotation().Inverse();
FVector TempEuler = AnimationCore::EulerFromQuat(HeadAimRotateAmount, EEulerRotationOrder::ZYX);
FVector XYZAnglesLimited = FVector(
FMath::Clamp(TempEuler.X, -30, 60), //俯仰角限制
TempEuler.Y,
FMath::Clamp(TempEuler.Z, -30, 30) //偏航角限制
);
FQuat TempQuat = AnimationCore::QuatFromEuler(XYZAnglesLimited);
// 用 SpringInterpQuaternionV2 把钳制后的旋转量做弹簧平滑。
SpringUnit.Target = TempQuat;
SpringUnit.Strength = SpringStrength;
SpringUnit.Execute(ExecuteContext);
// 最终的头部朝向 = 限制后的头部旋转量 + 头部骨骼的Rotation
Result = HeadAfterAim_Initial;
Result.SetRotation( SpringUnit.Result * HeadTransform.GetRotation() );
}
#pragma endregion

浙公网安备 33010602011771号