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

- Name
- 东哥
本文是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的核心算法在引擎插件AnimationWarping的FAnimNode_StrideWarping中:
Engine/Plugins/Animation/AnimationWarping/Source/Runtime/Public/BoneControllers/AnimNode_StrideWarping.hEngine/Plugins/Animation/AnimationWarping/Source/Runtime/Private/BoneControllers/AnimNode_StrideWarping.cpp
AnimationLocomotionLibrary和LyraAnim暴露的是"怎么配""什么时候开""开多少", 算法本体在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模式前, 必须确认:
- 动画序列带有Root Motion数据(root骨骼有位移信息)
- AnimGraph中Root Motion能正确传递到Stride Warping节点所在的位置
- 如果
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, 防止极低速下出现怪异扭曲
两种模式的关键差异
| 维度 | Manual | Graph |
|---|---|---|
| 步幅方向 | 外部手动提供StrideDirection | 自动从Root Motion Delta推导 |
| 步幅倍数 | 外部手动提供StrideScale | 自动计算LocomotionSpeed/RootMotionSpeed |
| Root Motion依赖 | 无 | 强依赖, 缺失则退化或退出 |
| 低速保护 | 无(完全看你给什么值) | 有MinRootMotionSpeedThreshold |
| Root Motion回写 | 不做 | 会把修正后的Delta写回属性流 |
| 适合场景 | 调试/实验/非标准运动 | 正常Locomotion循环 |
常见坑
Manual模式:
StrideDirection方向空间搞错 → 脚"斜着拉"。这个值是组件空间(Component Space), Mesh的Forward经常和Actor的Forward不一致, Mesh Forward可能指向角色右侧StrideScale过大(>2) → 橡皮腿, Stride Warping是修步幅不是跨栏- 以为Manual更稳定 → 实际上把引擎帮你做的速度比值逻辑全接管了, 大多数正常移动状态Graph更省心
- 低速/静止时忘记收回StrideScale → 脚在原地也被拉出去
Graph模式:
- 没有有效Root Motion信息 ← 最常见坑, "节点开了像没开"的第一大根源。动画序列必须带Root Motion数据, 且AnimGraph流程中能传递到本节点
LocomotionSpeed和动画本身速度语义不一致 → 比如角色速度是冲刺但动画还是普通jog, 比值激进, 腿迈过大- 低速下误以为没生效 →
MinRootMotionSpeedThreshold(默认10.0)会导致低速时收回1.0, 这是故意的, 不是bug - 图里Root Motion来源和预期不一致 → AnimGraph结构复杂时, 混合/层叠前后谁提供Root Motion Delta可能和直觉不同
- Root Motion Delta的
DeltaTime不一致 →LocomotionSpeed应该是相对动画图DeltaTime的速度, 不是固定帧率下的速度
计算流程
核心计算全部在FAnimNode_StrideWarping::EvaluateSkeletalControl_AnyThread中, 按顺序分以下几步:
1. 解析地面法线和重力方向
节点先把FloorNormalDirection和GravityDirection从各自的坐标空间转换到组件空间:
// 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脚根骨骼的局部空间
};
默认FloorNormalDirection是WorldSpaceVector + FVector::UpVector, GravityDirection是WorldSpaceVector + 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的逻辑:
- 如果
bClampResult为true, 把ActualStrideScale钳制在[ClampMin, ClampMax] - 如果
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);
}
}
可以帮你"尽量迈大", 但不能真把角色的腿当弹力绳
关键参数
评估模式参数
| 参数 | 类型 | 默认值 | 含义 |
|---|---|---|---|
Mode | EWarpingEvaluationMode | Manual | 评估模式。正常Locomotion循环优先Graph, 单独验证节点用Manual |
StrideDirection | FVector | ForwardVector | 步幅方向(组件空间), 仅Manual模式使用。方向空间搞错会斜拉 |
StrideScale | float | 1.0 | 步幅倍数, 仅Manual模式使用。1.0=原步幅, 先从小范围调 |
LocomotionSpeed | float | 0.0 | 角色当前真实移动速度, 主要用于Graph模式。注意应该是相对动画图DeltaTime的速度, 且要和当前动画表达的步态一致 |
MinRootMotionSpeedThreshold | float | 10.0 | 根运动速度阈值(默认10.0)。低于此值强制StrideScale=1.0。低速抖动时先看它, 不要为了"让低速也有明显效果"盲目压低 |
骨骼配置参数
| 参数 | 含义 |
|---|---|
PelvisBone | 骨盆骨骼, 做下拉补偿用。如果这里不对, 结果不是"稍微差一点", 而是整条腿链和身体重心都不可信 |
IKFootRootBone | IK脚链根节点参考。和角色Rig不匹配会导致左右脚补偿逻辑怪异, 某一侧特别容易出问题 |
FootDefinitions | 每只脚一组, 包含IKFootBone/FKFootBone/ThighBone。最关键的配置之一 |
FootDefinitions的结构:
struct FStrideWarpingFootDefinition
{
FBoneReference IKFootBone; // IK驱动的脚骨骼 (如 ik_foot_l)
FBoneReference FKFootBone; // FK驱动的脚骨骼 (如 foot_l)
FBoneReference ThighBone; // 大腿骨骼 (如 thigh_l), 代表腿链中骨盆之前的末端
};
如果配错会出现: 脚位置能动但腿姿态不跟、大腿旋转补偿方向异常、左右脚行为明显不对称。
建议: 先确保左右脚骨骼链定义完全对称, 再调参数, 不要在骨骼引用还没确认前就怀疑算法。
高级选项参数
| 参数 | 类型 | 默认值 | 含义 |
|---|---|---|---|
FloorNormalDirection | FWarpingVectorValue | WorldSpace, UpVector | 地面法线方向来源, 支持4种坐标空间。上下坡时差异明显 |
GravityDirection | FWarpingVectorValue | WorldSpace, DownVector | 重力方向来源, 常规项目默认向下 |
bOrientStrideDirectionUsingFloorNormal | bool | true | 是否结合地面法线矫正步幅方向, 正常地面移动建议开 |
bCompensateIKUsingFKThighRotation | bool | true | 是否用大腿FK旋转补偿IK脚位置变化, 大多数正常角色建议开 |
bClampIKUsingFKLimits | bool | true | IK目标钳制在FK腿长内, 防止超伸。真实项目建议开 |
bDisableIfMissingRootMotion | bool | true | 缺Root Motion时直接禁用节点, 类似保险丝 |
StrideScaleModifier (FInputClampConstants)
对最终步幅比例做二次处理:
| 属性 | 类型 | 默认值 | 含义 |
|---|---|---|---|
bClampResult | bool | false | 是否钳制StrideScale到[ClampMin, ClampMax] |
bInterpResult | bool | false | 是否对StrideScale做插值平滑 |
ClampMin | float | 0.0 | 钳制下限 |
ClampMax | float | 1.0 | 钳制上限 |
InterpSpeedIncreasing | float | 10.0 | 值增大时的插值速度 |
InterpSpeedDecreasing | float | 10.0 | 值减小时的插值速度 |
插值做了增加/减少方向分离, 可以让步幅放大时快速响应(InterpSpeedIncreasing大), 收回时缓慢(InterpSpeedDecreasing小), 避免腿部抖动。
感觉"理论上对但看起来不稳定"时优先检查这个。
PelvisIKFootSolver (FIKFootPelvisPullDownSolver)
控制骨盆怎么补偿脚步拉伸:
| 属性 | 类型 | 默认值 | 含义 |
|---|---|---|---|
PelvisAdjustmentInterp | FVectorRK4SpringInterpolator | — | RK4弹簧插值器参数, 控制骨盆下拉的时间平滑 |
PelvisAdjustmentInterpAlpha | double | 0.5 | 最终下拉量混合权重。0=完全保留原始骨盆位置, 1=完全应用计算下拉 |
PelvisAdjustmentMaxDistance | double | 10.0 | 骨盆最大下拉距离(UE单位) |
PelvisAdjustmentErrorTolerance | double | 0.01 | 迭代收敛误差容忍(1cm), 值越小质量越高但越慢 |
PelvisAdjustmentMaxIter | int32 | 3 | 最大迭代次数 |
常见问题: 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.hPlugins/LyraAnim/Source/LyraAnim/Data/LAAnimConfigDataAsset.hPlugins/LyraAnim/Source/LyraAnim/Core/LAAnimInstance.cppSource/LocomotionTemplate/UI/SLTDebugPanel.cpp
全局开关
ULASettings按移动阶段拆开控制:
bEnableStartStrideWarpingbEnableStopStrideWarpingbEnableCycleStrideWarpingbEnablePivotStrideWarping
不是一刀切全局常开, 不同状态下是否开启、权重多大应该区别对待。
DataAsset参数
LAAnimConfigDataAsset控制"什么时候淡入""某一类状态下权重有多大":
StrideWarpingBlendInDurationScaledStrideWarpingBlendInStartOffsetStartStrideWarpingWeight/StopStrideWarpingWeightCycleStrideWarpingWeight/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/角色比例不规整的角色
总结
Stride Warping是"运动一致性修补器", 不是从零生成高质量步态, 而是把真实速度和动画步幅之间的差距补掉- 输入越干净输出越好, 输入越乱结果越诡异 — Root Motion不稳定/速度语义混乱/骨骼链没配对都会导致"完全不像人走路"
- 它是工程节点不是美术魔法: 读输入→算比例→改脚目标→拉骨盆→转大腿→做钳制, 没有玄学步骤
- 能不能好用, 关键不在节点本身, 更在Root Motion、速度输入、骨骼链配置、补偿参数有没有统一逻辑
- Manual模式的
StrideDirection是组件空间, Mesh的Forward不一定等于角色面朝方向 - Graph模式要求动画必须带Root Motion数据, 否则节点直接退化或不执行
附录: 源码重点
FAnimNode_StrideWarping::EvaluateSkeletalControl_AnyThreadMode == Graph时对Root Motion Delta的读取ActualStrideScale计算路径StrideScaleModifier的钳制和插值PelvisIKFootSolver骨盆下拉逻辑bCompensateIKUsingFKThighRotation补偿逻辑bClampIKUsingFKLimits限制逻辑