Published on

动画:Stride Warping原理与源码分析(UE5.7)


本文是UE5.7版本的Stride Warping源码级分析。 基础概念(脚步缩放原理、骨盆校正、大腿骨校正、Debug图形说明)在旧版文档中已有详细讲解(含GIF对比), 本文不再重复。本文聚焦于UE5.7引擎源码中FAnimNode_StrideWarping两种工作模式、Graph模式的Root Motion Provider机制、新增参数体系、以及与旧版算法的差异


前言

Stride Warping解决的不是"动画播多快", 而是"这一步该迈多大"

基础概念和PlayRate vs Stride Warping的区别请参考旧版文档, 这里不再赘述。本文直接从UE5.7的源码结构开始。

核心定位

UE5.7里Stride Warping的核心算法在引擎插件AnimationWarpingFAnimNode_StrideWarping中:

  • Engine/Plugins/Animation/AnimationWarping/Source/Runtime/Public/BoneControllers/AnimNode_StrideWarping.h
  • Engine/Plugins/Animation/AnimationWarping/Source/Runtime/Private/BoneControllers/AnimNode_StrideWarping.cpp

AnimationLocomotionLibraryLyraAnim暴露的是"怎么配""什么时候开""开多少", 算法本体在AnimationWarping里:

项目 AnimBP / DataAsset / Debug 开关
      喂给 Stride Warping 节点参数
Engine 的 FAnimNode_StrideWarping 执行腿部修正

两种工作模式

UE5.7的FAnimNode_StrideWarping通过EWarpingEvaluationMode枚举区分两种模式:

// BoneControllerTypes.h
enum class EWarpingEvaluationMode : uint8
{
    Manual,  // 用户手动提供方向和缩放
    Graph    // 从动画图的Root Motion Delta自动推导
};

Manual模式

Manual模式下, 节点完全依赖外部提供的两个核心参数:

  • StrideDirection — 步幅方向向量, 默认值为FVector::ForwardVector(即<1,0,0>)
  • StrideScale — 步幅缩放倍数。1.0为原步幅, 0.5缩小一半, 2.0放大一倍。有ClampMin="0.0"约束

重要: StrideDirection是组件空间(Component Space), 不是世界空间, 也不是Actor空间。

对于UE的角色来说, Mesh组件的Forward和Actor的Forward经常不一致 — 最常见的陷阱是: Mesh的Forward Vector实际指向角色的右侧。这意味着如果你直接填FVector::ForwardVector, 步幅拉伸方向可能不是角色面朝方向, 而是侧向, 导致脚"斜着拉"或"横向拉"。

正确做法是先确认你的Mesh骨骼朝向, 再决定填什么方向。可以通过Debug模式(a.AnimNode.StrideWarping.Debug 1)看红色箭头确认实际步幅方向。

源码中Manual模式的数据流非常直接 — EvaluateSkeletalControl_AnyThread开头直接赋值:

// AnimNode_StrideWarping.cpp
ActualStrideDirection = StrideDirection;  // 直接用外部输入
ActualStrideScale = StrideScale;          // 直接用外部输入

也就是说Manual模式下节点的"大脑"完全在外部, 它只负责执行空间修正。

适合场景: 对输入有明确控制、调试节点是否工作、做实验性调节、非标准Locomotion场景(比如特殊运动模式)。

Graph模式

Graph模式是主力模式, 节点自动从AnimGraph的属性流中提取Root Motion Delta来推导参数。

重要: Graph模式强依赖动画的Root Motion数据。如果动画序列(AnimSequence)本身没有Root Motion, 或者AnimGraph流程中拿不到有效的Root Motion Delta, Graph模式会直接退化甚至完全不执行。

这是"节点开了但像没开"的第一大根源。用Graph模式前, 必须确认:

  1. 动画序列带有Root Motion数据(root骨骼有位移信息)
  2. AnimGraph中Root Motion能正确传递到Stride Warping节点所在的位置
  3. 如果bDisableIfMissingRootMotion为true(默认), 没有Root Motion时节点直接return, 什么都不做

核心机制 — Root Motion Provider:

// AnimNode_StrideWarping.cpp
const UE::Anim::IAnimRootMotionProvider* RootMotionProvider = UE::Anim::IAnimRootMotionProvider::Get();

if (Mode == EWarpingEvaluationMode::Graph)
{
    bGraphDrivenWarping = !!RootMotionProvider;
    ensureMsgf(bGraphDrivenWarping, TEXT("Graph driven Stride Warping expected a valid root motion delta provider interface."));
}

Graph模式依赖IAnimRootMotionProvider接口。如果这个接口获取不到, 或者属性流中没有Root Motion Delta数据, 整个Graph模式就直接退化。 源码中可以看到两个关键退出点:

方向推导 — 从Root Motion Delta提取:

FTransform RootMotionTransformDelta = FTransform::Identity;
if (bGraphDrivenWarping)
{
    // 从属性流中提取Root Motion Delta
    bGraphDrivenWarping = RootMotionProvider->ExtractRootMotion(Output.CustomAttributes, RootMotionTransformDelta);
    if (bGraphDrivenWarping)
    {
        CachedRootMotionDeltaTranslation = RootMotionTransformDelta.GetTranslation();
        // 用Root Motion的平移方向作为步幅方向; 如果没有有效位移, 保持上一帧方向
        ActualStrideDirection = CachedRootMotionDeltaTranslation.GetSafeNormal(UE_SMALL_NUMBER, PreviousStrideDirection);
    }
    // 没有Root Motion属性 且 bDisableIfMissingRootMotion=true → 直接return, 节点不执行
    else if (bDisableIfMissingRootMotion)
    {
        return;
    }
}

关键点:

  • 步幅方向ActualStrideDirection是从Root Motion Delta的平移分量归一化而来, 不是随便塞个世界前向
  • 如果Root Motion Delta为空(比如当前帧没有根运动位移), 会保持上一帧的方向PreviousStrideDirection做平滑过渡
  • 如果bDisableIfMissingRootMotion为true且完全拿不到Root Motion属性, 节点直接退出, 所有后续计算都不执行

缩放推导 — LocomotionSpeed / RootMotionSpeed:

if (bGraphDrivenWarping)
{
    const float CachedRootMotionDeltaSpeed = CachedRootMotionDeltaTranslation.Size() / CachedDeltaTime;

    if (CachedRootMotionDeltaSpeed <= MinRootMotionSpeedThreshold)
    {
        // 低于阈值, 不做激进步幅缩放, 收回1.0
        ActualStrideScale = 1.0f;
    }
    else
    {
        // 核心: 真实移动速度 / 动画根运动速度
        ActualStrideScale = LocomotionSpeed / CachedRootMotionDeltaSpeed;
    }
}

核心公式:

ActualStrideScale = LocomotionSpeed / RootMotionSpeed

  • LocomotionSpeed是你提供的角色当前真实移动速度(来自胶囊体/角色移动组件)
  • RootMotionSpeed是从Root Motion Delta算出的动画隐含速度(DeltaTranslation.Size() / DeltaTime)
  • 比值>1 → 真实移动比动画快 → 迈大步
  • 比值<1 → 真实移动比动画慢 → 迈小步
  • 低于MinRootMotionSpeedThreshold时强制回1.0, 防止极低速下出现怪异扭曲

两种模式的关键差异

维度ManualGraph
步幅方向外部手动提供StrideDirection自动从Root Motion Delta推导
步幅倍数外部手动提供StrideScale自动计算LocomotionSpeed/RootMotionSpeed
Root Motion依赖强依赖, 缺失则退化或退出
低速保护无(完全看你给什么值)MinRootMotionSpeedThreshold
Root Motion回写不做会把修正后的Delta写回属性流
适合场景调试/实验/非标准运动正常Locomotion循环

常见坑

Manual模式:

  1. StrideDirection方向空间搞错 → 脚"斜着拉"。这个值是组件空间(Component Space), Mesh的Forward经常和Actor的Forward不一致, Mesh Forward可能指向角色右侧
  2. StrideScale过大(>2) → 橡皮腿, Stride Warping是修步幅不是跨栏
  3. 以为Manual更稳定 → 实际上把引擎帮你做的速度比值逻辑全接管了, 大多数正常移动状态Graph更省心
  4. 低速/静止时忘记收回StrideScale → 脚在原地也被拉出去

Graph模式:

  1. 没有有效Root Motion信息最常见坑, "节点开了像没开"的第一大根源。动画序列必须带Root Motion数据, 且AnimGraph流程中能传递到本节点
  2. LocomotionSpeed和动画本身速度语义不一致 → 比如角色速度是冲刺但动画还是普通jog, 比值激进, 腿迈过大
  3. 低速下误以为没生效 → MinRootMotionSpeedThreshold(默认10.0)会导致低速时收回1.0, 这是故意的, 不是bug
  4. 图里Root Motion来源和预期不一致 → AnimGraph结构复杂时, 混合/层叠前后谁提供Root Motion Delta可能和直觉不同
  5. Root Motion Delta的DeltaTime不一致 → LocomotionSpeed应该是相对动画图DeltaTime的速度, 不是固定帧率下的速度

计算流程

核心计算全部在FAnimNode_StrideWarping::EvaluateSkeletalControl_AnyThread中, 按顺序分以下几步:

1. 解析地面法线和重力方向

节点先把FloorNormalDirectionGravityDirection从各自的坐标空间转换到组件空间:

// AnimNode_StrideWarping.cpp
const FTransform IKFootRootTransform = Output.Pose.GetComponentSpaceTransform(
    IKFootRootBone.GetCompactPoseIndex(RequiredBones));

// 将配置的空间方向统一转换为组件空间方向
const FVector ResolvedFloorNormal = FloorNormalDirection.AsComponentSpaceDirection(
    AnimInstanceProxy, IKFootRootTransform);
const FVector ResolvedGravityDirection = GravityDirection.AsComponentSpaceDirection(
    AnimInstanceProxy, IKFootRootTransform);

FWarpingVectorValue支持4种坐标空间:

// BoneControllerTypes.h
enum class EWarpingVectorMode : uint8
{
    ComponentSpaceVector,      // 组件空间
    ActorSpaceVector,          // Actor空间
    WorldSpaceVector,          // 世界空间(默认)
    IKFootRootLocalSpaceVector // IK脚根骨骼的局部空间
};

默认FloorNormalDirectionWorldSpaceVector + FVector::UpVector, GravityDirectionWorldSpaceVector + FVector::DownVector

2. 地面法线矫正步幅方向

如果开启bOrientStrideDirectionUsingFloorNormal, 会用叉积重新投影步幅方向到地面平面:

if (bOrientStrideDirectionUsingFloorNormal)
{
    // 步幅方向 × 地面法线 = 旋转轴
    const FVector StrideWarpingAxis = ResolvedFloorNormal ^ ActualStrideDirection;
    // 旋转轴 × 地面法线 = 投影后的步幅方向
    ActualStrideDirection = StrideWarpingAxis ^ ResolvedFloorNormal;
}

效果: 步幅方向被约束在地面平面上, 不再是简单沿Forward Vector直推。上下坡时"前进方向"和"贴地前进方向"不同, 这个选项确保步幅始终贴地。

3. StrideScaleModifier二次处理

无论Manual还是Graph模式, 算出来的ActualStrideScale都会经过StrideScaleModifier处理:

// 两种模式都会经过这个二次处理
ActualStrideScale = StrideScaleModifierState.ApplyTo(StrideScaleModifier, ActualStrideScale, CachedDeltaTime);

FInputClampConstants的定义:

// InputScaleBias.h
struct FInputClampConstants
{
    bool bClampResult = false;            // 是否钳制结果
    bool bInterpResult = false;           // 是否插值平滑
    float ClampMin = 0.f;                 // 钳制下限
    float ClampMax = 1.f;                 // 钳制上限
    float InterpSpeedIncreasing = 10.f;   // 值增大时的插值速度
    float InterpSpeedDecreasing = 10.f;   // 值减小时的插值速度
};

FInputClampState::ApplyTo的逻辑:

  1. 如果bClampResult为true, 把ActualStrideScale钳制在[ClampMin, ClampMax]
  2. 如果bInterpResult为true, 对值做增加/减少方向的独立插值平滑, 防止速度变化时腿部抖动

这是"理论上对但看起来不稳定"时优先检查的地方。

4. Graph模式回写Root Motion

这一步很多人会忽略。 Graph模式下, 节点会把修正后的Root Motion Delta写回属性流:

if (bGraphDrivenWarping)
{
    // 把Root Motion的平移部分按步幅缩放
    RootMotionTransformDelta.ScaleTranslation(ActualStrideScale);
    // 写回属性流, 影响后续所有使用Root Motion的系统
    const bool bRootMotionOverridden = RootMotionProvider->OverrideRootMotion(
        RootMotionTransformDelta, Output.CustomAttributes);
    ensureMsgf(bRootMotionOverridden,
        TEXT("Graph driven Stride Warping expected a root motion delta to be present "
             "in the attribute stream prior to warping/overriding it."));
}

这意味着Stride Warping不只是改骨骼视觉, 还会把修正后的运动信息传递给下游(角色移动组件等), 保持整套运动数据的一致性。

5. IK脚位置拉伸

核心公式和旧版文档一致: WarpedFoot = ScaleOrigin + (IKFoot - ScaleOrigin) * ActualStrideScale

UE5.7相比旧版的差异在于步幅缩放平面原点的计算 — 旧版用HipBone(臀部), UE5.7改用ThighBone(大腿):

// UE5.7: 从大腿骨骼沿重力方向射线, 与(IK脚+地面法线)平面的交点
const FVector StrideWarpingPlaneOrigin =
    (FMath::Abs(ResolvedGravityDirection | ResolvedFloorNormal) > DELTA)
    ? FMath::LinePlaneIntersection(ThighBoneLocation, ThighBoneLocation + ResolvedGravityDirection, IKFootLocation, ResolvedFloorNormal)
    : IKFootLocation;

// 投影到步幅方向平面得到缩放原点
const FVector ScaleOrigin = FVector::PointPlaneProject(IKFootLocation, StrideWarpingPlaneOrigin, ActualStrideDirection);

// 经典缩放公式(和旧版一致)
const FVector WarpedLocation = ScaleOrigin + (IKFootLocation - ScaleOrigin) * ActualStrideScale;

注意: 旧版(UE4/早期UE5)代码中有一个bug — 缩放平面法线用的是StrideWarpingPlaneNormal(固定方向), 导致某些朝向下脚步原地踏步。UE5.7改为用动态的ActualStrideDirection作为平面法线, 修复了这个问题。详见旧版文档中的魔改代码对比。

6. 骨盆下拉补偿

只改脚不动骨盆, 腿会被拉超伸。 迭代求解的思路和旧版文档一致: 每只脚贡献一个"理想骨盆位置"(IK脚 + 方向 * FK腿长), 多脚取加权平均, 迭代到收敛。

UE5.7相比旧版的改进:

  • 旧版用简单插值, UE5.7改用RK4弹簧插值器(FVectorRK4SpringInterpolator), 平滑效果更好
  • 新增PelvisAdjustmentInterpAlpha控制保留多少原始骨盆运动(旧版的PelvisPostAdjustmentAlpha)
  • 新增PelvisAdjustmentMaxDistance硬性限制下拉距离
  • 新增PelvisAdjustmentErrorTolerance控制迭代收敛精度

求解器完整参数见下方PelvisIKFootSolver参数表

常见误区: "Stride Warping把腿扭坏了" → 实际是脚被拉出去后骨盆补偿过强(PelvisAdjustmentInterpAlpha太大)或下拉距离过大(PelvisAdjustmentMaxDistance太大), 看着像塌腰或矮了一截。

7. 大腿旋转补偿

思路和旧版文档一致: 大腿跟着骨盆偏移后, 用FQuat::FindBetweenNormals算出从FK方向到IK方向的旋转差, 应用到大腿骨骼。

UE5.7额外新增了FK长度限制(bClampIKUsingFKLimits): 如果IK脚拉伸后超过FK腿长, 钳制到FK腿长范围内, 防止超伸。不开大腿补偿的话容易出现脚落点变了但腿上半段姿态没顺过去, 膝盖方向怪。

8. FK长度限制

bClampIKUsingFKLimits在IK脚拉伸后检查是否超过了FK腿长极限:

if (bClampIKUsingFKLimits)
{
    // FK腿长 = 大腿到FK脚的距离
    const float FKLength = FVector::Dist(FKFootTransform.GetLocation(), ThighTransform.GetLocation());
    // IK腿长 = 偏移后大腿到IK脚的距离
    const float IKLength = FVector::Dist(Foot.IKFootBoneTransform.GetLocation(), AdjustedThighTransform.GetLocation());

    if (IKLength > FKLength)
    {
        // 超过FK腿长, 钳制到FK腿长范围内
        const FVector ClampedFootLocation = AdjustedThighTransform.GetLocation() + TargetDir * FKLength;
        Foot.IKFootBoneTransform.SetLocation(ClampedFootLocation);
    }
}

可以帮你"尽量迈大", 但不能真把角色的腿当弹力绳

关键参数

评估模式参数

参数类型默认值含义
ModeEWarpingEvaluationModeManual评估模式。正常Locomotion循环优先Graph, 单独验证节点用Manual
StrideDirectionFVectorForwardVector步幅方向(组件空间), 仅Manual模式使用。方向空间搞错会斜拉
StrideScalefloat1.0步幅倍数, 仅Manual模式使用。1.0=原步幅, 先从小范围调
LocomotionSpeedfloat0.0角色当前真实移动速度, 主要用于Graph模式。注意应该是相对动画图DeltaTime的速度, 且要和当前动画表达的步态一致
MinRootMotionSpeedThresholdfloat10.0根运动速度阈值(默认10.0)。低于此值强制StrideScale=1.0。低速抖动时先看它, 不要为了"让低速也有明显效果"盲目压低

骨骼配置参数

参数含义
PelvisBone骨盆骨骼, 做下拉补偿用。如果这里不对, 结果不是"稍微差一点", 而是整条腿链和身体重心都不可信
IKFootRootBoneIK脚链根节点参考。和角色Rig不匹配会导致左右脚补偿逻辑怪异, 某一侧特别容易出问题
FootDefinitions每只脚一组, 包含IKFootBone/FKFootBone/ThighBone。最关键的配置之一

FootDefinitions的结构:

struct FStrideWarpingFootDefinition
{
    FBoneReference IKFootBone;   // IK驱动的脚骨骼 (如 ik_foot_l)
    FBoneReference FKFootBone;   // FK驱动的脚骨骼 (如 foot_l)
    FBoneReference ThighBone;    // 大腿骨骼 (如 thigh_l), 代表腿链中骨盆之前的末端
};

如果配错会出现: 脚位置能动但腿姿态不跟、大腿旋转补偿方向异常、左右脚行为明显不对称。

建议: 先确保左右脚骨骼链定义完全对称, 再调参数, 不要在骨骼引用还没确认前就怀疑算法。

高级选项参数

参数类型默认值含义
FloorNormalDirectionFWarpingVectorValueWorldSpace, UpVector地面法线方向来源, 支持4种坐标空间。上下坡时差异明显
GravityDirectionFWarpingVectorValueWorldSpace, DownVector重力方向来源, 常规项目默认向下
bOrientStrideDirectionUsingFloorNormalbooltrue是否结合地面法线矫正步幅方向, 正常地面移动建议开
bCompensateIKUsingFKThighRotationbooltrue是否用大腿FK旋转补偿IK脚位置变化, 大多数正常角色建议开
bClampIKUsingFKLimitsbooltrueIK目标钳制在FK腿长内, 防止超伸。真实项目建议开
bDisableIfMissingRootMotionbooltrue缺Root Motion时直接禁用节点, 类似保险丝

StrideScaleModifier (FInputClampConstants)

对最终步幅比例做二次处理:

属性类型默认值含义
bClampResultboolfalse是否钳制StrideScale到[ClampMin, ClampMax]
bInterpResultboolfalse是否对StrideScale做插值平滑
ClampMinfloat0.0钳制下限
ClampMaxfloat1.0钳制上限
InterpSpeedIncreasingfloat10.0值增大时的插值速度
InterpSpeedDecreasingfloat10.0值减小时的插值速度

插值做了增加/减少方向分离, 可以让步幅放大时快速响应(InterpSpeedIncreasing大), 收回时缓慢(InterpSpeedDecreasing小), 避免腿部抖动。

感觉"理论上对但看起来不稳定"时优先检查这个。

PelvisIKFootSolver (FIKFootPelvisPullDownSolver)

控制骨盆怎么补偿脚步拉伸:

属性类型默认值含义
PelvisAdjustmentInterpFVectorRK4SpringInterpolatorRK4弹簧插值器参数, 控制骨盆下拉的时间平滑
PelvisAdjustmentInterpAlphadouble0.5最终下拉量混合权重。0=完全保留原始骨盆位置, 1=完全应用计算下拉
PelvisAdjustmentMaxDistancedouble10.0骨盆最大下拉距离(UE单位)
PelvisAdjustmentErrorTolerancedouble0.01迭代收敛误差容忍(1cm), 值越小质量越高但越慢
PelvisAdjustmentMaxIterint323最大迭代次数

常见问题: PelvisAdjustmentInterpAlpha太猛角色像蹲了, 太小腿拉直发硬。没有通用数值, 必须结合角色比例和动画风格调整。

调试命令

a.AnimNode.StrideWarping.Debug 1     // 开启可视化调试
a.AnimNode.StrideWarping.Verbose 1   // 开启详细日志(显示Root Motion Delta Speed, Locomotion Speed等)
a.AnimNode.StrideWarping.Enable 0    // 关闭Stride Warping

Debug图形说明(红色/绿色线、球含义)详见旧版文档的总结部分。

项目接入

算法本体在引擎, 开关和权重管理在LyraAnim配置层:

  • Plugins/LyraAnim/Source/LyraAnim/Data/LASettings.h
  • Plugins/LyraAnim/Source/LyraAnim/Data/LAAnimConfigDataAsset.h
  • Plugins/LyraAnim/Source/LyraAnim/Core/LAAnimInstance.cpp
  • Source/LocomotionTemplate/UI/SLTDebugPanel.cpp

全局开关

ULASettings按移动阶段拆开控制:

  • bEnableStartStrideWarping
  • bEnableStopStrideWarping
  • bEnableCycleStrideWarping
  • bEnablePivotStrideWarping

不是一刀切全局常开, 不同状态下是否开启、权重多大应该区别对待。

DataAsset参数

LAAnimConfigDataAsset控制"什么时候淡入""某一类状态下权重有多大":

  • StrideWarpingBlendInDurationScaled
  • StrideWarpingBlendInStartOffset
  • StartStrideWarpingWeight / StopStrideWarpingWeight
  • CycleStrideWarpingWeight / PivotStrideWarpingWeight

Stride Warping最怕两件事: 一上来直接吃满, 所有状态一套参数打天下。

排查顺序

"开了Stride Warping但效果不对"的排查步骤:

第1步: 确认节点有没有在工作

  • 当前状态是否走到了这个节点?
  • 当前模式Manual还是Graph?
  • Graph模式下, 动画序列是否带Root Motion数据? AnimGraph中Root Motion能否传递到本节点? (用a.AnimNode.StrideWarping.Verbose 1查看Root Motion Delta Attribute是否找到)

第2步: 看速度语义

  • LocomotionSpeed什么来源?
  • 当前动画表达的步态是什么?
  • 两者是否在说同一件事?

第3步: 看骨骼链配置

  • PelvisBone / 左右脚FootDefinitions / IKFootBone/FKFootBone/ThighBone是否对称

第4步: 看步幅比例

  • ActualStrideScale是否过大或过小
  • 是否需要Clamp或平滑插值

第5步: 看骨盆和大腿补偿

  • 骨盆下拉是否过多
  • 大腿旋转补偿是否让姿态更顺

不要反过来一上来就猛调权重, 很容易把输入问题误判成参数问题。

适用场景

适合:

  • 循环移动动画, 已有稳定Root Motion参考的步态
  • 想保留动画原节奏, 不想靠Play Rate硬拉
  • 速度和步幅之间存在中等程度偏差

不适合单独指望它:

  • 动画本身重心和落脚质量差
  • 启动/停止等依赖时机定位的问题
  • 极端速度跨度
  • 骨骼/IK/角色比例不规整的角色

总结

  1. Stride Warping是"运动一致性修补器", 不是从零生成高质量步态, 而是把真实速度和动画步幅之间的差距补掉
  2. 输入越干净输出越好, 输入越乱结果越诡异 — Root Motion不稳定/速度语义混乱/骨骼链没配对都会导致"完全不像人走路"
  3. 它是工程节点不是美术魔法: 读输入→算比例→改脚目标→拉骨盆→转大腿→做钳制, 没有玄学步骤
  4. 能不能好用, 关键不在节点本身, 更在Root Motion、速度输入、骨骼链配置、补偿参数有没有统一逻辑
  5. Manual模式的StrideDirection是组件空间, Mesh的Forward不一定等于角色面朝方向
  6. Graph模式要求动画必须带Root Motion数据, 否则节点直接退化或不执行

附录: 源码重点

  • FAnimNode_StrideWarping::EvaluateSkeletalControl_AnyThread
  • Mode == Graph时对Root Motion Delta的读取
  • ActualStrideScale计算路径
  • StrideScaleModifier的钳制和插值
  • PelvisIKFootSolver骨盆下拉逻辑
  • bCompensateIKUsingFKThighRotation补偿逻辑
  • bClampIKUsingFKLimits限制逻辑