角色动作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游戏开发13 小时前
Unity3D 使用 ILRuntime 时的性能问题详解
前端·unity3d·游戏开发
Bluesonli3 天前
第 16 天:游戏 UI(UMG)开发,打造主菜单 & 血条!
学习·游戏·ui·ue5·虚幻·unreal engine
学游戏开发的4 天前
UE求职Demo开发日志#32 优化#1 交互逻辑实现接口、提取Bag和Warehouse的父类
c++·笔记·游戏引擎·unreal engine
Bluesonli5 天前
第 14 天:UE5 C++ 与蓝图(Blueprint)交互!
c++·游戏·ue5·交互·unreal engine
学游戏开发的6 天前
UE求职Demo开发日志#29 继续流程实现
笔记·游戏引擎·unreal engine
Thomas游戏开发8 天前
Unity3D游戏排行榜制作与优化技术详解
前端框架·unity3d·游戏开发
山东布谷科技官方8 天前
小游戏源码开发之可跨app软件对接是如何设计和开发的
游戏开发·游戏源码·小游戏开发·小游戏源码·直播间小游戏·语音房小游戏
Bluesonli9 天前
第 9 天:UE5 物理系统 & 碰撞检测全解析!
开发语言·学习·游戏·ue5·虚幻·unreal engine
Bluesonli9 天前
第 10 天:UE5 交互系统,拾取物品 & 触发机关!
学习·游戏·ue5·虚幻·unreal engine
Thomas游戏开发10 天前
Unity3D 架构师如何处理大世界地图技术详解
前端框架·unity3d·游戏开发