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()TargetSpringState 等形参。
  • 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_METHODFRigUnit / FRigVMFunction_SimBase 派生 USTRUCT 都能这么调:
FRigUnit_SpringInterp*FRigUnit_SetCurveValueFRigUnit_GetTransform……


真实示例(HybridAnim 项目)

.hFRigUnit_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

.cppFRigUnit_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
posted @ 2026-07-29 11:16  EanoJiang  阅读(41)  评论(0)    收藏  举报