문제 상황
- ALS 캐릭터 액터의 Scale을 늘렸을 때, 캐릭터의 로코모션 애니메이션의 PlayRate가 보정되지 않는 현상
PlayRate 연산 로직
CalculateStandingPlayRate
- ALS는 캐릭터 스케일에 따라 애니메이션의 PlayRate를 조정한다
float UALSCharacterAnimInstance::GetAnimCurveClamped(const FName& Name, float Bias, float ClampMin, float ClampMax) const
{
return FMath::Clamp(GetCurveValue(Name) + Bias, ClampMin, ClampMax);
}
// ...
float UALSCharacterAnimInstance::CalculateStandingPlayRate() const
{
// Calculate the Play Rate by dividing the Character's speed by the Animated Speed for each gait.
// The lerps are determined by the "W_Gait" anim curve that exists on every locomotion cycle so
// that the play rate is always in sync with the currently blended animation.
// The value is also divided by the Stride Blend and the mesh scale so that the play rate increases as the stride or scale gets smaller
const float LerpedSpeed = FMath::Lerp(CharacterInformation.Speed / Config.AnimatedWalkSpeed,
CharacterInformation.Speed / Config.AnimatedRunSpeed,
GetAnimCurveClamped(NAME_W_Gait, -1.0f, 0.0f, 1.0f));
const float SprintAffectedSpeed = FMath::Lerp(LerpedSpeed, CharacterInformation.Speed / Config.AnimatedSprintSpeed,
GetAnimCurveClamped(NAME_W_Gait, -2.0f, 0.0f, 1.0f));
const float ComponentScaleZ = GetOwningComponent()->GetComponentScale().Z;
return FMath::Clamp((SprintAffectedSpeed / Grounded.StrideBlend) / ComponentScaleZ, 0.0f, 3.0f);
}
- LerpedSpeed
- W_Gait는 각 애니메이션에서 고정값으로 할당한다
- GetAnimCurveClamped함수에서 Bias가 -1로 할당되어 있다.
- Gait가 Walk(1)이면 CharacterInformation.Speed / Config.AnimatedWalkSpeed 가 LerpedSpeed에 할당된다
- Gait가 Run(2) 또는 Sprint(3)이면 CharacterInformation.Speed / Config.AnimatedRunSpeed 가 LerpedSpeed에 할당된다
- SprintAffectedSpeed
- Bias가 -2로 할당되어 있다
- Gait가 Sprint(3)라면 CharacterInformation.Speed / Config.AnimatedSprintSpeed가 SprintAffectedSpeed에 할당된다
- 그 외의 경우에는 LerpedSpeed가 SprintAffectedSpeed에 할당된다
- 즉, SprintAffectedSpeed는 캐릭터 실제 속도에 기반한 애니메이션의 PlayRate 수치이다.
- 마지막으로 최종 값 반환 시, SprintAffectedSpeed는 Grounded.StrideBlend로 나누고, 다시 ComponentScaleZ로 나눈다
- 스케일만 커졌다고 가정했을 때, 기존 이동 거리를 같은 시간 안에 이동하므로 애니메이션의 PlayRate는 기존보다 더 느려진다
- 즉, 캐릭터 Scale가 PlayRate가 반비례하다
CalculateStrideBlend

- StrideBlend는 ALS Character의 Walk와 Run으로 이루어진 BlendSpace에서 두 Gait 애니메이션 사이의 보폭을 결정하는 변수로 작동한다
- (N) Locomotion Cycles State 안에 Walk / Run을 위해 할당된 BlendSpace에서 확인 가능
float UALSCharacterAnimInstance::CalculateStrideBlend() const
{
// Calculate the Stride Blend. This value is used within the blendspaces to scale the stride (distance feet travel)
// so that the character can walk or run at different movement speeds.
// It also allows the walk or run gait animations to blend independently while still matching the animation speed to
// the movement speed, preventing the character from needing to play a half walk+half run blend.
// The curves are used to map the stride amount to the speed for maximum control.
const float CurveTime = CharacterInformation.Speed / GetOwningComponent()->GetComponentScale().Z;
const float ClampedGait = GetAnimCurveClamped(NAME_W_Gait, -1.0, 0.0f, 1.0f);
const float LerpedStrideBlend =
FMath::Lerp(StrideBlend_N_Walk->GetFloatValue(CurveTime), StrideBlend_N_Run->GetFloatValue(CurveTime), ClampedGait);
return FMath::Lerp(LerpedStrideBlend, StrideBlend_C_Walk->GetFloatValue(CharacterInformation.Speed), GetCurveValue(NAME_BasePose_CLF));
}
- CurveTime
- 캐릭터 스케일에 기반한 CurveTime을 구한다
- 이 CurveTime은 특정 데이터 에셋에 접근에 Value을 가져오는 Key의 역할이다
- ClampedGait
- Gait 값이 고정이므로 Lerp Alpha는 Walk인 경우 0, Run인 경우 1이다
- LerpedStrideBlend
- 커브 에셋 StrideBlend_N_Walk 혹은 StrideBlend_N_Run에서 CurveTime에 대응하는 값을 가져온다
- 위의 두 에셋은 0 ~ 1 사이로 정의된 커브 데이터이다
- StrideBlend_N_Walk는 멈춤과 Walk 사이의 보폭을 0 ~ 1 사이의 값으로 반환한다
- StrideBlend_N_Run는 Walk와 Run 사이의 보폭을 0 ~ 1 사이의 값으로 반환한다
- 마지막으로 최종 값 반환 시, 프로젝트 기획 상 Crouch 상태는 고려하지 않는다
- 즉, 항상 LerpedStrideBlend가 반환된다
return FMath::Clamp((SprintAffectedSpeed / Grounded.StrideBlend) / ComponentScaleZ, 0.0f, 3.0f);
- 보폭이 짧아진다면, 동일한 속도로 이동하기 위해 애니메이션의 PlayRate는 기존보다 더 빨라진다
- 즉, StrideBlend도 PlayRate와 반비례하다
해결 방안
Curve Time
const float CurveTime = CharacterInformation.Speed / GetOwningComponent()->GetComponentScale().Z;
- CalculateStandingPlayRate에서 CurveTime을 구하는 식은 위와 같다
const float LerpedStrideBlend =
FMath::Lerp(StrideBlend_N_Walk->GetFloatValue(CurveTime), StrideBlend_N_Run->GetFloatValue(CurveTime), ClampedGait);
- 그리고 CurveTime으로 각 커브에서 대응하는 최종 값을 가져온다
- 즉, 캐릭터 스케일이 클수록 CurveTime의 값이 작아지므로 StrideBlend 커브에셋에서 가져오는 값도 작아진다
return FMath::Clamp((SprintAffectedSpeed / Grounded.StrideBlend) / ComponentScaleZ, 0.0f, 3.0f);
- 이는 최종 연산되는 StrideBlend의 값을 작게 만들고, PlayRate를 더 빠르게 만든다
- 커브 에셋을 수정하지 않으면, 캐릭터 스케일이 저킬수록 PlayRate가 빨라지므로 발이 미끄러지는 현상이 발생
- 이를 보정하지 위해, 커브 에셋의 값을 수정한다
커브 에셋 수정

- 이를 테면 캐릭터의 스케일을 10배 키웠고, 위 그래프와 같이 StrideBlend_N_Run 에셋의 Y축 1에 대응하는 X축 값이 기존 600이라면,

- 위 그래프처럼 Y축 1에 대응하는 X축 값을 60으로 조정해준다
- StrideBlend_N_Walk도 마찬가지다
- 단순히 발이 미끄러지는 현상에 대응하기 위해서는 ALSConfig의 StrideBlend 커브 에셋을 모두 Y축 1에 대응하는 값을 (기존 값) * (1 / 스케일 값)으로 조장해야 한다
- 발이 미끄러지는 현상을 막을 수는 있지만 보간의 영역에 가깝다
- 스케일이 커지는 만큼 PlayRate은 느려질 것이고
- 스케일이 작아진다면 PlayRate 더 빨라질 것
- 궁극적으로 애니메이터와 기획자가 의도한 애니메이션이 스케일을 조정한 이후에도 유지되는지 검토가 필요
- 수정한 스케일에 맞는 새로운 애니메이션이 작업되는 것이 더 자연스러울 것이다.