Distance Matching
Distance Matching

通过角色与目标点的距离和动画根节点移动的距离进行匹配,可以使角色脚步准确落到目标点上,已解决滑步的问题。这个技术一般应用在角色的起步、停步、急停、下落反馈中。
具体的使用可以查看:
这个功能在Animation Locomotion Library
插件中,插件中主要代码文件只有三个:
- AnimCharacterMovementLibrary:预测角色移动库
- AnimDistanceMatchingLibrary:距离匹配库
- DistanceCurveModifier:生成距离曲线变形器
下面简单讲解一下其中的主要函数的逻辑
AnimCharacterMovementLibrary
PredictGroundMovementStopLocation
static FVector PredictGroundMovementStopLocation(const FVector& Velocity,
bool bUseSeparateBrakingFriction, float BrakingFriction, float GroundFriction, float BrakingFrictionFactor, float BrakingDecelerationWalking);
函数用于预测一个物体在地面运动时的停止位置,基于其当前速度、摩擦力、减速等参数,参数都可以在UCharacterMovementComponent
中找到。以下是公式的推导和解释:
在代码中,初始位置被忽略(假设为 FVector::ZeroVector
),因此公式简化为:
其中:
- Velocity2D 是物体在水平面(XY平面)上的速度。
- TimeToStop 是物体停止所需的时间。
- Acceleration 是物体的减速度(负加速度)。
计算物体在水平面(XY平面)上的速度(Velocity2D)
- Velocity2D = Velocity * FVector(1.f, 1.f, 0.f)
- Speed2D = Speed2D.Size()
计算停止时间(TimeToStop)
停止时间由当前速度和减速度决定:
其中:
- Speed2D 是物体在水平面上的速度大小。
- Divisor 是摩擦力和减速的综合效果:
- ActualBrakingFriction 是实际摩擦力,由
BrakingFriction
或GroundFriction
决定,并乘以BrakingFrictionFactor
。 - BrakingDeceleration 是额外的减速度(
BrakingDecelerationWalking
)。
计算物体的减速度(Acceleration)
由摩擦力和额外减速度组成:
最终公式可以总结为:
PredictGroundMovementPivotLocation
static FVector PredictGroundMovementPivotLocation(const FVector& Acceleration, const FVector& Velocity, float GroundFriction);
通过加速度方向预测角色急停改变的方向,首先计算速度在加速度上的分量(也就是点乘),如果小于零,说明速度和加速度反方向那就需要急停。计算公式同样也是物理中的匀加速运动公式:
在代码中,初始位置被忽略(假设为 FVector::ZeroVector
),因此公式简化为:
其中:
- Velocity 是物体的初始速度。
- TimeToDirectionChange 是物体运动方向发生变化所需的时间。
- AccelerationForce 是物体的净加速度(包括外部加速度和摩擦力)。
(1) 提取水平加速度(Acceleration2D)
- 忽略垂直分量(Z轴),只保留水平加速度(XY平面)。
(2) 计算速度在加速度方向上的分量(VelocityAlongAcceleration)
- 使用点积计算速度在加速度方向上的投影:
- 如果
VelocityAlongAcceleration < 0
,表示速度方向与加速度方向相反,物体会减速并最终改变方向。
(3) 计算方向变化时间(TimeToDirectionChange)
- 方向变化时间由以下公式计算:
其中:
- SpeedAlongAcceleration 是速度在加速度方向上的大小(取反,因为
VelocityAlongAcceleration < 0
):
- Divisor 是加速度和摩擦力的综合效果:
(4) 计算净加速度(AccelerationForce)
- 净加速度由外部加速度和摩擦力共同决定:
(5) 计算转向点位置(PredictedPivotLocation)
- 使用匀加速运动公式计算转向点位置:
最终公式可以总结为:
AnimDistanceMatchingLibrary
DistanceMatchToTarget
static FSequenceEvaluatorReference DistanceMatchToTarget(const FSequenceEvaluatorReference& SequenceEvaluator,
float DistanceToTarget, FName DistanceCurveName);
使用动画曲线和距离变量来选择动画姿势。其核心算法在UE::Anim::DistanceMatchingUtility::GetAnimPositionFromDistance
。通过二分法查找曲线中最接近DistanceToTarget
的关键帧:
int32 First = 1;
int32 Last = NumKeys - 1;
int32 Count = Last - First;
while (Count > 0)
{
int32 Step = Count / 2;
int32 Middle = First + Step;
if (InDistance > BufferCurveAccess.GetValue(Middle))
{
First = Middle + 1;
Count -= Step + 1;
}
else
{
Count = Step;
}
}
然后找到关键帧对应的动画时间返回即可:
const float KeyAValue = BufferCurveAccess.GetValue(First - 1);
const float KeyBValue = BufferCurveAccess.GetValue(First);
const float Diff = KeyBValue - KeyAValue;
const float Alpha = !FMath::IsNearlyZero(Diff) ? ((InDistance - KeyAValue) / Diff) : 0.f;
const float KeyATime = BufferCurveAccess.GetTime(First - 1);
const float KeyBTime = BufferCurveAccess.GetTime(First);
return FMath::Lerp(KeyATime, KeyBTime, Alpha);
AdvanceTimeByDistanceMatching
static FSequenceEvaluatorReference AdvanceTimeByDistanceMatching(const FAnimUpdateContext& UpdateContext, const FSequenceEvaluatorReference& SequenceEvaluator,
float DistanceTraveled, FName DistanceCurveName, FVector2D PlayRateClamp = FVector2D(0.75f, 1.25f));
按角色每帧行进的距离将连接的 Sequence Evaluator
节点向前推进而不是时间。
核心逻辑在UE::Anim::DistanceMatchingUtility::GetTimeAfterDistanceTraveled
,其主要思想是从动画当前时间点开始,以30pfs
的采样率推进动画,并累加推进动画产生的距离,该距离由曲线获得。当距离大于等于DistanceTraveled
时,返回采样点,该点的时间就是需要前进动画的时间点。
SetPlayrateToMatchSpeed
static FSequencePlayerReference SetPlayrateToMatchSpeed(const FSequencePlayerReference& SequencePlayer,
float SpeedToMatch, FVector2D PlayRateClamp = FVector2D(0.75f, 1.25f));
该函数通过调整动画序列播放器(FAnimNode_SequencePlayer
)的播放速率(Play Rate
),使动画的根运动速度与给定的目标速度(SpeedToMatch
)匹配。计算方式如下:
// Calculate the speed as: (distance traveled by the animation) / (length of the animation)
const FVector RootMotionTranslation = AnimSequence->ExtractRootMotionFromRange(0.0f, AnimLength).GetTranslation();
const float RootMotionDistance = RootMotionTranslation.Size2D();
if (!FMath::IsNearlyZero(RootMotionDistance))
{
const float AnimationSpeed = RootMotionDistance / AnimLength;
float DesiredPlayRate = SpeedToMatch / AnimationSpeed;
if (PlayRateClamp.X >= 0.0f && PlayRateClamp.X < PlayRateClamp.Y)
{
DesiredPlayRate = FMath::Clamp(DesiredPlayRate, PlayRateClamp.X, PlayRateClamp.Y);
}
InSequencePlayer.SetPlayRate(DesiredPlayRate);
}
DistanceCurveModifier
使用UAnimSequence::ExtractRootMotion
方法提取根节点的位移,并记录到曲线中。