写在前面
在接下来的几个部分中,我们将来处理角色的动作,在此期间我们会用上动画蓝图。在这篇文章中,我们会来处理角色Equip的动作。
动作,意味着什么呢,我们在之前的动画蓝图动画蓝图中已经说过,动作是由状态来决定的------ 一个状态决定了一个动作,不同状态之间的转变,决定了动作的转变。在没有装备上武器之前,我们是空手的状态,而在装备上武器之后,我们就会进入装备上武器之后的步行状态。
玩过一些FPS的同学都知道,比如说《战地》,以及刚出的《幻兽帕鲁》,一般而言,在持枪状态下,我们的角色朝向会和controller和摄像一致,前后左右的移动不再是人直接面朝前后左右直接进行移动,而是面朝控制器,双脚进行移动,这样才可以在逻辑上保持人物的瞄准方向和玩家的观感一致。
状态准备------是否装备了武器
首先,我们需要一个状态位来确定角色是否已经进入了武器装备状态。这个状态位当然在AnimeInstance里,毕竟我们会在我们的动画蓝图中用。
C++
// BlasterAnimation.h
UPROPERTY(BlueprintReadOnly, Category = Character, meta = (AllowPrivateAccess = "true"))
bool bIsEquippedWeapon;
// BlasterAnimation.cpp
if (BlasterCharacter->GetEquipWeaponComponent() && BlasterCharacter->GetEquipWeaponComponent()->GetEquippedWeapon())
bIsEquippedWeapon = true; // 是否装备了武器
逻辑非常简单,我们确认下角色的战斗组件里面是否已经有装备武器了,有的话,那就说明已经有武器装备了。
但是,这里有个问题,在装备武器那一章节我们已经提到过,我们为了保持同步,我们在客户端装备武器时,我们本质是让服务端去帮我们做事,我们客户端的战斗模块实例上的EquipWeapon其实只在服务端进行了更新(UE帮我们已经做了很多同步行为,但是貌似在一些细节上还需要我们自己同步),这就意味着,客户端的AnimeInstance上依赖于EquipWeaponComponent组件的状态位是没法更新的。
那怎么办的?依然是老办法,Property Replicated
, 将服务端的EquipWeaponComponent上的EquipWeapon直接同步给客户端的就得了。
c++
// 装备的武器
UPROPERTY(Replicated, VisibleAnyWhere)
AWeapon *EquippedWeapon;
void UEquipWeaponComponent::GetLifetimeReplicatedProps(TArray<FLifetimeProperty> &OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
// EquippedWeapon 同步
DOREPLIFETIME(ThisClass, EquippedWeapon);
}
这样客户端和服务端的动画蓝图都能检测到正确的装备状态了。
状态准备------奔跑方向和倾斜
我们已经说过,在装备了武器时,我们的角色的奔跑逻辑会和没有装备武器时很不相同,我们得做到角色面部朝向的同时,让角色的腿部动作往前后左右移动,同时在角色转向时,我们可以给予角色一个倾斜的效果,因此我们得设置出Direction和Lean两个量来关联动作。
C++
// BlasterAnimation.h
// 移动方向
UPROPERTY(BlueprintReadOnly, Category = Character, meta = (AllowPrivateAccess = "true"))
float Direction;
UPROPERTY(BlueprintReadOnly, Category = Character, meta = (AllowPrivateAccess = "true"))
float Lean; // 倾斜度
FRotator CharacterRotationLastFrame;
FRotator CharacterRotation;
FRotator DeltaRotation;
// BlasterAnimation.cpp
void UBlasterAnimation::NativeUpdateAnimation(float DeltaSeconds)
{
Super::NativeUpdateAnimation(DeltaSeconds);
// ......
auto AimRotation = BlasterCharacter->GetBaseAimRotation(); // 你照相机的视角还有Aim offset 全局
auto MovementRotation = UKismetMathLibrary::MakeRotFromX(BlasterCharacter->GetVelocity()); // 你角色mesh的朝向 全局
auto DeltaRot = UKismetMathLibrary::NormalizedDeltaRotator(AimRotation, MovementRotation); // 你controller的方向 角度 - 你角色方向角度 = 你要去走的角度
DeltaRotation = FMath::RInterpTo(DeltaRotation, DeltaRot, DeltaSeconds, 6.f); // 插值
Direction = DeltaRotation.Yaw; // 你要去走的角度的Yaw
CharacterRotationLastFrame = CharacterRotation;
CharacterRotation = BlasterCharacter->GetActorRotation();
const auto Delta = UKismetMathLibrary::NormalizedDeltaRotator(CharacterRotation, CharacterRotationLastFrame);
const float Target = Delta.Yaw / DeltaSeconds;
const float Interp = FMath::FInterpTo(Lean, Target, DeltaSeconds, 6.f);
Lean = FMath::Clamp(Interp, -90, 90);
}
可以这么理解,Direction的计算方式主要取决于你角色本身的瞄准方向
和角色的移动方向
的差,角色的瞄准方向和你的屏幕的方向是一致的,角色移动的方向就是你键盘按下的方向,两个的差就是最后的方向。
第二个Lean是由加速度来得到的,在这一帧我是朝向正面的,到了下一帧我朝向了左面,我们计算出二者之间转化的加速度,加速度越大就越斜,然后把加速度給映射到-90-90的范围内。怎么样,很合理吧!
当然,为了让之间的转变更自然,两者都加入了插值。所谓插值就是中间值,举个例子,我以非常快的速度从正面转到了后面,那势必会显得非常突兀,那我们可以在这两者中间加一个中间值,比如说左边,这样一来,玩家在快速转向时,就可以看到转动的一个中间状态,虽然时间是拉长了那么一点,但是效果就变好了。
再拿Lean倾斜举例子,我从左边突然歪到右边,是不是也很突兀?因为缺乏中间值,玩家会感觉人物在抖动,非常影响观感,那我也加一个插值就可以缓解了。
动画准备------建立角色移动混合空间
状态准备完毕后,那就该动画出场了。
首先,在角色的基础动画包里面找到基础的拿枪移动动画,然后复制一份,接着我们要对他们进行左右倾斜调整。 比如:
到
让它歪向左边或右边20度左右。
首先,点开Skeleton Tree按钮,接着点击root:
然后就可以调整了,导出时,点击key,然后导出
对于每个hip进行如此操作后,我们会得到大约12份动画。
建立Blend Space
, 以Direction(-180-180)
为横轴,Lean(-90-90)
为纵轴。把这些动画贴上即可。
动画蓝图
首先在动画蓝图第一层,我们通过判断是否装备武器来判断进入哪个状态机。
在Equipped状态机中,我们把Standing状态和Crouching状态分开。
在Idle内,我们添加普通的站立Idle动画,在Jog内,我们加入刚才的混合空间。
Idle -> Jog
Jog->Idle
注意我们在写状态转变时,可以尽可能地加入多个状态判断来缩减转变范围。