角色动作Equip, Crouch,Aim(3)

写在前面

这一节开始,我们开始着手AimOffset,一个特殊的Blend Space,用来产生角色瞄准时的头部转向和枪口瞄准效果。但在完成效果之前,我觉得我们还有必要认识一下Addictive Animation

Addictive Animation

说是可加动画,但其实是在先做减法再做加法。

首先,我们会有一个Base Pose------比如说向前跑,然后我们使用当前动画身体前倾得向前跑

那我们拿身体前倾得向前跑-向前跑 = 身体前倾

此时,我们把这个可加动画直接应用于持枪向前跑,那我们就可以得到身体前倾得持枪向前跑

还记得我们之前在做Equip Animation时的Lean参数吗,当时我们为了得到每个动作的Lean,把每一个动作都做了一个左右倾斜的副本用在BlendSpace时,但其实那样的效果不仅麻烦,而且还很差。如果我们做两个Addictive Animation,分别是向左倾斜向前跑, 向右倾斜向前跑,把它们的Base Pose设定为向前跑,接着再把这两个动作做成Blend Space应用于向后跑,向右跑等动作,我们就会得到倾斜着向后跑倾斜着向右跑等动作。很合理吧?

制作 AimOffset Asset

制作AimOffset的素材,我们需要每个动作的Addictive Animation

在UE提供的Animation Pack中,我们可以对现成的Animation Sequence进行切片来得到

只要在你想切除的特定的帧,切除之前帧再切除之后帧即可,只保留当前帧。

最后,我们得到各个方向的动画,每个动画只有一帧:

接着我们对每个动画进行Addictive Type进行批量设置:

这里的Base Pose统一都是角色的正前方,也就是Center Center

接着我们开始制作AimOffset Space

横轴为Yaw即角色瞄准的水平旋转,纵轴为角色瞄准的纵轴旋转。

获取角色瞄准的Yaw和Pitch

首先我们要确定AOYaw,当我们角色在奔跑时,不需要瞄准偏移,所以我们只有在原地不动时才会有AO_Yaw的需求,而这个Yaw总是代表的瞄准方向和角色瞄准方向的偏移。同时,我们都知道这个Yaw和Pitch都是时刻更新的,所以我们尽量不去使用Replicated系统,避免客户端和Server的信息交换占用太多带宽,然而可惜的是,我发现Yaw不得不由Server进行转发,因为它似乎并没有和Pitch一样UE已经帮我们同步好了。

c++ 复制代码
// BlasterCharacter.h
UPROPERTY(Replicated)
float AO_Yaw;
float AO_Pitch;

FRotator StartAimRotation;
FRotator CurrentAimRotation;
FRotator DeltaRotation;

// 计算 Aim Yaw 和 Aim Pitch
void CalcAimOffset(float DeltaSeconds);


// BlasterCharacter.cpp
void ABlasterCharacter::CalcAimOffset(float DeltaSeconds)
{

	// 如果手中没有武器 直接退出
	if (!EquipWeaponModule || !EquipWeaponModule->GetEquippedWeapon())
		return;

	auto Speed = FVector(GetVelocity().X, GetVelocity().Y, 0.f).Size();
	auto IsInAir = this->GetCharacterMovement()->IsFalling();
  // 当角色不移动时
	if (Speed < 3 && !IsInAir)
	{
		// 将Yaw交给Server去更新
		if (HasAuthority())
		{
			CurrentAimRotation = FRotator(0, GetBaseAimRotation().Yaw, 0);
			auto ActorYaw = StartAimRotation.Yaw;
			auto DeltaRot = UKismetMathLibrary::NormalizedDeltaRotator(StartAimRotation, CurrentAimRotation);

			this->AO_Yaw = DeltaRot.Yaw;
		}

		this->bUseControllerRotationYaw = false;
	}

	// 在空中时或者在步行时 得到当前瞄准方向
	if (Speed >= 3 || IsInAir)
	{
		if (HasAuthority())
		{
			StartAimRotation = FRotator(0, GetBaseAimRotation().Yaw, 0);
			this->AO_Yaw = 0;
		}
		this->bUseControllerRotationYaw = true;
	}

	this->AO_Pitch = GetBaseAimRotation().Pitch;

	// 如果Pitch大于90度,那么我们需要将其转换为-90到0度 这是因为UE的打包压缩算法 会把负值转化为无符号数
	if (AO_Pitch > 90 && !IsLocallyControlled())
	{
		FVector2D InRange = FVector2D(270, 360);
		FVector2D OutRange = FVector2D(-90, 0);

		AO_Pitch = FMath::GetMappedRangeValueClamped(InRange, OutRange, AO_Pitch);
	}
}

void ABlasterCharacter::GetLifetimeReplicatedProps(TArray<FLifetimeProperty> &OutLifetimeProps) const
{
  // ...

	// 这里的属性是在客户端和服务器之间进行同步的
	DOREPLIFETIME(ABlasterCharacter, AO_Yaw);
}

void ABlasterCharacter::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);
	CalcAimOffset(DeltaTime);
}

你会注意到,我们的AO_Yaw是由当前的角色的瞄准方向和角色移动完最后停留的完整方向构成的,同时如果角色没有武器,该逻辑就不进行,这样一来就会有隐藏的问题: 角色如果在不装备武器的状态下到装备武器,那么StartAimRotation会是0,这意味着AO_Yaw会在装备上枪的那一刻变得很大,但其实我们期望是0才对。解决方案其实很简单,一种是那我在角色装备上武器函数里更新StartAimRotation为当前的AimRotation或者直接用ActorRotation(其实我个人觉得这个才是正解)。

动画蓝图

那么接下来我们开始构建动画蓝图,我们在一开始说过,AimOffset的本质是一个Addictive Animation,它需要基于Base Pose才能发挥作用,同时,我们还需要保证角色下半身动作不会受到AimOffset动画的影响(有待试验,我感觉没啥区别)。

相关推荐
懷淰メ18 小时前
PyQt飞机大战游戏(附下载地址)
开发语言·python·qt·游戏·pyqt·游戏开发·pyqt5
Thomas游戏开发2 天前
Unity3D 逻辑服的Entity, ComponentData与System划分详解
前端框架·unity3d·游戏开发
异次元的归来6 天前
UE5的线程同步机制
ue5·游戏引擎·unreal engine
Thomas_YXQ19 天前
Unity3D中管理Shader效果详解
开发语言·游戏·unity·unity3d·游戏开发
Ljw...22 天前
C++游戏开发
c++·c·游戏开发
AgilityBaby1 个月前
UE5蓝图中整理节点的方法
ue5·游戏引擎·unreal engine·1024程序员节
北冥没有鱼啊1 个月前
ue5 扇形射线检测和鼠标拖拽物体
游戏·ue5·ue4·游戏开发·虚幻
孙_华鹏1 个月前
高德地图游戏解决方案——结合threejs仿某猫捉猫猫得红包游戏
前端·typescript·游戏开发
会做游戏的小蜗牛1 个月前
从零到一:前端开发者学习 Cocos Creator 的全攻略
前端·游戏开发
黑不溜秋的1 个月前
染引擎实践 - UnrealEngine引擎中RHI实现分析
unreal engine