原地旋转
效果是,角色在静止的时候,并且是瞄准状态下。视角移动,角色会触发一个踏步动作然后旋转。
在Tick中如果是瞄准状态下 CanRotateInPlace() == true
触发RotateInPlaceCheck
void UCwlBaseAnimIns::UpdateGroundValues()
{
bShouldMove = CheckCanMove();
if (IsFromStopToMove(bLastShouldMove, bShouldMove))
{
bRotateL = bRotateR = false;
}
bLastShouldMove = bShouldMove;
if (bShouldMove)
{
// ...
}
else
{
if (CanRotateInPlace())
{
RotateInPlaceCheck();
}
else
{
bRotateL = bRotateR = false;
}
// ...
}
}
bool UCwlBaseAnimIns::IsFromStopToMove(bool bInLastShouldMove, bool bInShouldMove) const
{
return !bInLastShouldMove && bInShouldMove;
}
bool UCwlBaseAnimIns::CanRotateInPlace()
{
return RotationMode == ECwlRotationMode::Aiming;
}
void UCwlBaseAnimIns::RotateInPlaceCheck()
{
// 根据 AnimingAngle 计算左转还是右转
// X 是 偏转值的Yaw 意思是左右
bRotateL = AnimingAngle.X < RotateMinThreshold;
bRotateR = AnimingAngle.X > RotateMaxThreshold;
// 鼠标转速变成角色转速 AnimYawRate -> RotateRate
if (bRotateL || bRotateR)
{
// 常量
RotateRate = UKismetMathLibrary::MapRangeClamped(AnimYawRate, AnimYawMinRange, AnimYawMaxRange, MinPlayRate, MaxPlayRate);
}
}
里面大多都是常量
const float RotateMinThreshold = -50;
const float RotateMaxThreshold = 50;
const float AnimYawMinRange = 90;
const float AnimYawMaxRange = 270;
const float MinPlayRate = 1.15;
const float MaxPlayRate = 3;
其中 AnimingAngle 计算的是每次控制旋转和角色旋转的插是多少。AimingRotation 是 Control Rotation
// 在当前角色的基础之上又偏了多少
FRotator DeltaRotation = AimingRotation - Character->GetActorRotation();
DeltaRotation.Normalize();
AnimingAngle.X = DeltaRotation.Yaw; // 左右
AnimingAngle.Y = DeltaRotation.Pitch; // 俯仰
动画状态机
绿色的条件是 bRotateL, 紫色的条件是 bRotate R
【Rotate L 90】和【Rotate R 90】在动画播放完毕
在旋转状态中,除了播放踏步动画以外。还会修改 Rotation Amount的值【注意这里modify是scale】。
看写这个动画Rotate_L_90的曲线 Rotation Amount。显然在脚步移动的时候,旋转比较快。非常真实。
这个数值会在角色没有移动时,AddActorWorldRotation 更新角色旋转
void ACwlCharacter::UpdateGroundedRotation()
{
// 这里做的事就是 character movement 的使用控制器旋转做的事情
float DeltaTime = GetWorld()->GetDeltaSeconds();
// ...
if (GetMovementAction() != ECwlMovementAction::None)
{
return ;
}
if (CanUpdateMovingRotation())
{
// ...
}
else
{
if (GetViewMode() == ECwlViewMode::ThirdPerson && GetRotationMode() == ECwlRotationMode::Aiming || GetViewMode() == ECwlViewMode::FirstPerson)
{
LimitRotation(-100, 100, 20);
}
const float RotAmountCurve = GetAnimCurveValue(TEXT("RotationAmount"));
if (FMath::Abs(RotAmountCurve) > 0.001)
{
float Yaw = RotAmountCurve * DeltaTime * 30;
AddActorWorldRotation(UKismetMathLibrary::MakeRotator(0, 0, Yaw));
}
TargetRotator = GetActorRotation();
}
}
原地转身
效果是非瞄准状态下,且位于LookingDirection状态下。视角看向角色后方,角色会自动触发一个转身动作
void UCwlBaseAnimIns::UpdateGroundValues()
{
// ...
if (bShouldMove)
{
// ...
}
else
{
// ...
if (CanTurnInPlace())
{
TurnInPlaceCheck();
}
else
{
ELapsedDelayTime = 0;
}
逻辑很简单 CanTurnInPlace() => TurnInPlaceCheck()
CanTurnInPlace的条件是 LookingDirection 且有 Enable_Transition 这条曲线。另外网络环境下 follower关闭这个功能。
bool UCwlBaseAnimIns::CanTurnInPlace()
{
if (bIsFollower)
{
return false;
}
return RotationMode == ECwlRotationMode::LookingDirection && GetCurveValue(TEXT("Enable_Transition")) > 0.99;
}
这条曲线位于 Not Moving状态机中。将Rotation Scale缩放应用于曲线Rotation Amount 最后应用到角色旋转上。
这里还有一个插槽,用于播放蒙太奇。
总结一下就是需要转身,播放蒙太奇,处理旋转。
void UCwlBaseAnimIns::TurnInPlaceCheck()
{
// 这里有BUG,DS下不同客户端表现不一样,还没修
if (FMath::Abs(AnimingAngle.X) > TurnCheckMinAngle && AnimYawRate < AimYawRateLimit)
{
ELapsedDelayTime = ELapsedDelayTime + GetWorld()->GetDeltaSeconds();
if (ELapsedDelayTime > UKismetMathLibrary::MapRangeClamped(
FMath::Abs(AnimingAngle.X), TurnCheckMinAngle, 180, MinAngleDelay, MaxAngleDelay))
{
TurnInPlace(UKismetMathLibrary::MakeRotator(0, 0, AimingRotation.Yaw), 1, 0, false);
}
}
else
{
ELapsedDelayTime = 0;
}
}
void UCwlBaseAnimIns::TurnInPlace(const FRotator &TargetRotation, float PlayRateScale, float StartTime, bool bOverrideCurrent)
{
float TurnAngle = UKismetMathLibrary::NormalizedDeltaRotator(TargetRotation, Character->GetActorRotation()).Yaw;
if (FMath::Abs(TurnAngle) < Turn180Threshold)
{
if (TurnAngle < 0)
{
switch (Stance)
{
case ECwlStance::Standing:
TargetTurnAsset = AlsAnimDataAsset->NTurnIpL90;
break;
case ECwlStance::Crouch:
TargetTurnAsset = AlsAnimDataAsset->ClfTurnIpL90;
break;
}
}
else
{
switch (Stance)
{
case ECwlStance::Standing:
TargetTurnAsset = AlsAnimDataAsset->NTurnIpR90;
break;
case ECwlStance::Crouch:
TargetTurnAsset = AlsAnimDataAsset->ClfTurnIpR90;
break;
}
}
}
else
{
if (TurnAngle < 0)
{
switch (Stance)
{
case ECwlStance::Standing:
TargetTurnAsset = AlsAnimDataAsset->NTurnIpL180;
break;
case ECwlStance::Crouch:
TargetTurnAsset = AlsAnimDataAsset->ClfTurnIpL180;
break;
}
}
else
{
switch (Stance)
{
case ECwlStance::Standing:
TargetTurnAsset = AlsAnimDataAsset->NTurnIpL180;
break;
case ECwlStance::Crouch:
TargetTurnAsset = AlsAnimDataAsset->ClfTurnIpL180;
break;
}
}
}
if (bOverrideCurrent || !IsPlayingSlotAnimation(TargetTurnAsset.Animation, TargetTurnAsset.SlotName))
{
PlaySlotAnimationAsDynamicMontage(TargetTurnAsset.Animation, TargetTurnAsset.SlotName, 0.2, 0.2,
TargetTurnAsset.PlayRate * PlayRateScale, 1.f, 0.f, StartTime);
if (TargetTurnAsset.bScaleTurnAngle)
{
RotationScale = (TurnAngle / TargetTurnAsset.AnimatedAngle) * TargetTurnAsset.PlayRate * PlayRateScale;
}
else
{
RotationScale = TargetTurnAsset.PlayRate * PlayRateScale;
}
}
}
动态过度
地面变化的时候,通过播放一个蒙太奇,作为过度。脚步IK再适应地面。
static const FName GName_VB_Foot_Target_L(TEXT("VB foot_target_l"));
static const FName GName_VB_Foot_Target_R(TEXT("VB foot_target_r"));
void UCwlBaseAnimIns::UpdateGroundValues()
{
// ...
if (bShouldMove)
{
// ...
}
else
{
if (CanDynamicTransition())
{
DynamicTransitionCheck();
}
else
{
}
}
}
bool UCwlBaseAnimIns::CanDynamicTransition()
{
return GetCurveValue(TEXT("Enable_Transition")) > 0.99;
}
void UCwlBaseAnimIns::DynamicTransitionCheck()
{
// 地面变化的时候,通过播放一个蒙太奇,作为过度。脚步IK再适应地面。
if (UKismetAnimationLibrary::K2_DistanceBetweenTwoSocketsAndMapRange(
GetOwningComponent(), IkFootLBoneName, RTS_Component, GName_VB_Foot_Target_L, RTS_Component,
false, 0, 0, 0, 0) > 8.f)
{
PlayDynamicTransition(0.1, AlsAnimDataAsset->DynamicMontageParamsL);
}
if (UKismetAnimationLibrary::K2_DistanceBetweenTwoSocketsAndMapRange(
GetOwningComponent(), IkFootRBoneName, RTS_Component, GName_VB_Foot_Target_R, RTS_Component,
false, 0, 0, 0, 0) > 8.f)
{
PlayDynamicTransition(0.1, AlsAnimDataAsset->DynamicMontageParamsR);
}
}