角色动作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动画的影响(有待试验,我感觉没啥区别)。

相关推荐
Thomas游戏开发2 天前
Unity3D 自动化游戏框架设计
前端框架·unity3d·游戏开发
oyishyi2 天前
从零开始独立游戏开发学习笔记(七十八)--绘画/像素画学习笔记(十五)--V大预科3.0(五)-第三,四周理论
游戏·游戏开发
LeeAt3 天前
《谁杀死了比尔?》:使用Trae完成的一个推理游戏项目!!
前端·游戏开发·trae
龙智DevSecOps解决方案3 天前
游戏开发中的CI/CD优化案例:知名游戏公司Gearbox使用TeamCity简化CI/CD流程
ci/cd·游戏开发·jetbrains·teamcity
一名用户4 天前
unity实现自定义粒子系统
c#·unity3d·游戏开发
技术小甜甜6 天前
【Blender Texture】【游戏开发】高质感 Blender 4K 材质资源推荐合集 —— 提升场景真实感与美术表现力
blender·游戏开发·材质·texture
Thomas游戏开发6 天前
Unity3D TextMeshPro终极使用指南
前端·unity3d·游戏开发
Thomas游戏开发7 天前
Unity3D 逻辑代码性能优化策略
前端框架·unity3d·游戏开发
Thomas游戏开发8 天前
Unity3D HUD高性能优化方案
前端框架·unity3d·游戏开发
学游戏开发的9 天前
Lyra学习笔记 Experience流程梳理
笔记·unreal engine