UE4开发C++沙盒游戏教程笔记(六)(对应教程集数 18 ~ 21)

17. 添加玩家模型与相机

在 Content 目录下新建一个 Blueprint 文件夹,用于放置蓝图;
然后在这个文件夹里新建一个 Temp 文件夹,用于临时放一些后期不用的蓝图文件。

一般写 C++ 的 Gameplay 类的时候,都会创建这个类的蓝图来动态预览 C++ 代码运行的效果。

玩家角色默认会有一个全身的骨骼网格体,可以作为第三人称角色外形。我们还需要添加如下要素:第三人称摄像机、第三人称摄像机的吊杆、第一人称的骨骼网格体、第一人称摄像机。

SlAiPlayerCharacter.h

public:
	//摄像机吊杆
	UPROPERTY(VisibleDefaultsOnly, Category = "SlAi")
	class USpringArmComponent* CameraBoom;
	// 第三人称摄像机
	UPROPERTY(VisibleDefaultsOnly, Category = "SlAi")
	class UCameraComponent* ThirdCamera;
	// 第一人称摄像机
	UPROPERTY(VisibleDefaultsOnly, Category = "SlAi")
	UCameraComponent* FirstCamera;
	
private:

	// 第一人称的骨骼模型
	UPROPERTY(VisibleDefaultsOnly, Category = "SlAi")
	USkeletalMeshComponent* MeshFirst;

SlAiPlayerCharacter.cpp

#include "SlAiPlayerCharacter.h"
// 添加头文件
#include "ConstructorHelpers.h"
#include "Engine/SkeletalMesh.h"
#include "Components/SkeletalMeshComponent.h"
#include "GameFramework/SpringArmComponent.h"
#include "Camera/CameraComponent.h"

ASlAiPlayerCharacter::ASlAiPlayerCharacter()
{
	PrimaryActorTick.bCanEverTick = true;

	// 添加第一人称骨骼模型
	static ConstructorHelpers::FObjectFinder<USkeletalMesh> StaticMeshFirst(TEXT
	("SkeletalMesh'/Game/Res/PolygonAdventure/Mannequin/FirstPlayer/SkMesh/FirstPlayer.FirstPlayer'"));
	MeshFirst = CreateDefaultSubobject<USkeletalMeshComponent>(TEXT("MeshFirst"));
	MeshFirst->SetSkeletalMesh(StaticMeshFirst.Object);
	MeshFirst->SetupAttachment((USceneComponent*)GetCapsuleComponent());
	MeshFirst->bOnlyOwnerSee = true;
	MeshFirst->bCastDynamicShadow = false;
	MeshFirst->bReceivesDecals = false;
	// 更新频率衰落(此处内容跟优化有关,感兴趣的读者可复制关键字到搜索引擎查找相关解释)	
	//MeshFirst->MeshComponentUpdateFlag = EMeshComponentUpdateFlag::OnlyTickPoseWhenRendered;
	// 笔者的 4.26.2 引擎,由于变量名有改动,所以把上面这行代码换成了下面的代码,看读者版本决定使用哪段代码
	MeshFirst->VisibilityBasedAnimTickOption = EVisibilityBasedAnimTickOption::OnlyTickPoseWhenRendered;
	MeshFirst->PrimaryComponentTick.TickGroup = TG_PrePhysics;
	// 设置碰撞属性
	MeshFirst->SetCollisionObjectType(ECC_Pawn);
	MeshFirst->SetCollisionEnabled(ECollisionEnabled::NoCollision);
	MeshFirst->SetCollisionResponseToAllChannels(ECR_Ignore);
	// 设置位移
	MeshFirst->SetRelativeLocation(FVector(0.f, 0.f, -95.f));
	MeshFirst->SetRelativeRotation(FQuat::MakeFromEuler(FVector(0.f, 0.f, -90.f)));


	// 给默认 Mesh 添加第三人称骨骼模型
	static ConstructorHelpers::FObjectFinder<USkeletalMesh> StaticMeshThird
		(TEXT("SkeletalMesh'/Game/Res/PolygonAdventure/Mannequin/Player/SkMesh/Player.Player'"));
	GetMesh()->SetSkeletalMesh(StaticMeshThird.Object);
	GetMesh()->bOnlyOwnerSee = true;	// 只有玩家控制器看得见
	GetMesh()->bReceivesDecals = false;	// 不接受物体光反射(节约资源)
	GetMesh()->SetCollisionObjectType(ECC_Pawn);
	GetMesh()->SetCollisionEnabled(ECollisionEnabled::NoCollision);
	GetMesh()->SetCollisionResponseToAllChannels(ECR_Ignore);
	GetMesh()->SetRelativeLocation(FVector(0.f, 0.f, -95.f));
	GetMesh()->SetRelativeRotation(FQuat::MakeFromEuler(FVector(0.f, 0.f, -90.f)));

	// 摄像机手臂
	CameraBoom = CreateDefaultSubobject<USpringArmComponent>(TEXT("CameraBoom"));
	CameraBoom->SetupAttachment(RootComponent);
	// 设置距离
	CameraBoom->TargetArmLength = 300.f;
	// 设置偏移
	CameraBoom->TargetOffset = FVector(0.f, 0.f, 60.f);
	// 绑定 Controller 的旋转
	CameraBoom->bUsePawnControlRotation = true;

	// 初始化第三人称摄像机
	ThirdCamera = CreateDefaultSubobject<UCameraComponent>(TEXT("ThirdCamera"));
	ThirdCamera->SetupAttachment(CameraBoom, USpringArmComponent::SocketName);
	// 设置 ThirdCamera 不跟随控制器的旋转
	ThirdCamera->bUsePawnControlRotation = false;

	// 初始化第一人称摄像机
	FirstCamera = CreateDefaultSubobject<UCameraComponent>(TEXT("FirstCamera"));
	FirstCamera->SetupAttachment((USceneComponent*)GetCapsuleComponent());
	// 设置跟随 Controller 的旋转
	FirstCamera->bUsePawnControlRotation = true;
	// 设置偏移
	FirstCamera->AddLocalOffset(FVector(0.f, 0.f, 60.f));
}

角色

在 Temp 文件夹里新建一个以 SlAiPlayerCharacter 为基类的蓝图,取名 PlayerCharacterBP。此时能在蓝图里看到第三人称骨骼和摄像机已经添加,模型位置及朝向正确,碰撞类型已调整。且有第一人称的双手骨骼和摄像机。

18. 动作蓝图与玩家移动

新建碰撞类型并应用

在项目设置的 Engine - Collision 新建一个碰撞类型,取名为 Player,默认回应为 Block。

下方 Preset 新建一个配置信息,如下图

碰撞配置

随后将新建的碰撞配置应用到玩家角色上

SlAiPlayerCharacter.cpp


// 添加头文件
#include "Components/CapsuleComponent.h"


ASlAiPlayerCharacter::ASlAiPlayerCharacter()
{
	PrimaryActorTick.bCanEverTick = true;

	// 设置人物碰撞体的属性为 PlayerProfile,下面的骨骼模型的碰撞就都可以设置为无碰撞
	GetCapsuleComponent()->SetCollisionProfileName(FName("PlayerProfile"));
}

运行后,可以看到角色的胶囊体的碰撞类型已经更改。

动画类需要通过角色的一些数值来决定采用什么动画,所以动画类本地需要有一些变量来存储这些数值。(这些数值来源于角色类)

动画基类添加通用的数据变量。

SlAiPlayerAnim.h

UCLASS()
class SLAICOURSE_API USlAiPlayerAnim : public UAnimInstance
{
public:

	USlAiPlayerAnim();

public:
	// 角色速度
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "PlayAnim")
	float Speed;
	// 脊柱旋转角度(用于调整上半身和下半身的动画混合)
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "PlayAnim")
	FRotator SpineRotator;
}

SlAiPlayerAnim.cpp

USlAiPlayerAnim::USlAiPlayerAnim()
{
	Speed = 0.f;
	SpineRotator = FRotator(0.f, 0.f, 0.f);
}

给第三人称动画类添加对应变量。

SlAiThirdPlayerAnim.h

UCLASS()
class SLAICOURSE_API USlAiThirdPlayerAnim : public USlAiPlayerAnim
{
	GENERATED_BODY()

public:

	USlAiThirdPlayerAnim();

public:
	// 是否在空中
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "PlayAnim")
	bool IsInAir;
	// 角色朝向
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "PlayAnim")
	float Direction;
	
};

SlAiThirdPlayerAnim.cpp

USlAiThirdPlayerAnim::USlAiThirdPlayerAnim()
{
	// 给方向赋值
	Direction = 0.f;
	// 给是否在空中赋值
	IsInAir = false;
}

在 Blueprint 目录下新建一个文件夹 Player,用来存放动画蓝图。
在里面创建第三人称动画类的蓝图,父类选 SlAiThirdPlayerAnim,骨骼选择 Player_Skeleton,命名为 ThirdPlayer_Animation

在第三人称动画蓝图里配置相应动画,最后得如图的动画状态机布局。

第三人称动画状态机
随后把第三人称动画蓝图配置给角色类。

SlAiPlayerCharacter.cpp

ASlAiPlayerCharacter::ASlAiPlayerCharacter()
{
	// ... 省略
	GetMesh()->SetRelativeRotation(FQuat::MakeFromEuler(FVector(0.f, 0.f, -90.f)));

	// 获取第三人称的动作蓝图(获取蓝图一般要在末尾加上"_C")
	static ConstructorHelpers::FClassFinder<UAnimInstance> StaticAnimThird(TEXT
		("AnimBlueprint'/Game/Blueprint/Player/ThirdPlayer_Animation.ThirdPlayer_Animation_C'"));
	GetMesh()->AnimClass = StaticAnimThird.Class;
	
}

此时运行后,可以看见第三人称动画蓝图已经配置到角色的第三人称网格体上。

第三人称动画应用
将上面的步骤复刻到第一人称动画类中。

在 Player 目录里面创建第一人称动画类的蓝图,父类选 SlAiFirstPlayerAnim,骨骼选择 FirstPlayer_Skeleton,命名为 FirstPlayer_Animation

动画状态机就只需要一个 Move 状态就行,这里就不放截图了。

将第一人称动画蓝图配置到角色类。

SlAiPlayerCharacter.cpp

ASlAiPlayerCharacter::ASlAiPlayerCharacter()
{
	// ... 省略
	MeshFirst->SetRelativeRotation(FQuat::MakeFromEuler(FVector(0.f, 0.f, -90.f)));

	// 获取第一人称的动作蓝图
	static ConstructorHelpers::FClassFinder<UAnimInstance> StaticAnimFirst(TEXT
("AnimBlueprint'/Game/Blueprint/Player/FirstPlayer_Animation.FirstPlayer_Animation_C'"));
	MeshFirst->AnimClass = StaticAnimFirst.Class;
	
}

编译后可看到,第一人称动画蓝图已经配置到角色的第一人称网格体上。

第一人称动画应用

添加控制角色的输入

要让角色响应输入,需要配置按键绑定以及添加控制移动逻辑。

配置按键如下:(老师的 MoveRight 左右数值设错了记得改过来)

按键绑定

PlayerCharacter 和 PlayerController 都是可以绑定按键输入的,但是为了管理方便,打开 UI 之类的输入放在 PlayerController;移动、转向等影响角色动作的输入放在 PlayerCharacter。

SlAiPlayerCharacter.h

UCLASS()
class SLAICOURSE_API ASlAiPlayerCharacter : public ACharacter
{
private:
	// 添加下列方法
	void MoveForward(float Value);
	void MoveRight(float Value);
	void LookUpAtRate(float Value);
	void Turn(float Value);
	void TurnAtRate(float Value);
	void OnStartJump();
	void OnStopJump();
	void OnStartRun();
	void OnStopRun();
};

SlAiPlayerCharacter.cpp

ASlAiPlayerCharacter::ASlAiPlayerCharacter()
{
	// ... 省略
	FirstCamera->AddLocalOffset(FVector(0.f, 0.f, 60.f));
	
	// 默认启用第三人称摄像机
	FirstCamera->SetActive(false);
	ThirdCamera->SetActive(true);
}

void ASlAiPlayerCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
	// 调用父类的同名方法,当使用引擎自带的方法时比较常见
	Super::SetupPlayerInputComponent(PlayerInputComponent);

	check(PlayerInputComponent);
	// 绑定按键和方法
	PlayerInputComponent->BindAxis("MoveForward", this, &ASlAiPlayerCharacter::MoveForward);
	PlayerInputComponent->BindAxis("MoveRight", this, &ASlAiPlayerCharacter::MoveRight);
}

void ASlAiPlayerCharacter::MoveForward(float Value)
{
	if (Value != 0 && Controller) {
		const FRotator Rotation = Controller->GetControlRotation();
		FVector Direction = FRotationMatrix(Rotation).GetScaledAxis(EAxis::X);
		AddMovementInput(Direction, Value);
	}
}

void ASlAiPlayerCharacter::MoveRight(float Value)
{
	if (Value != 0) {
		const FQuat Rotation = GetActorQuat();
		FVector Direction = FQuatRotationMatrix(Rotation).GetScaledAxis(EAxis::Y);
		AddMovementInput(Direction, Value);
	}
}

// ... 对于头文件里声明到的其他方法,后续会继续补充,此处补充一下它们的空定义就行,就不贴出来了

此时运行后使用 WASD 键可以操控角色前左后右移动,但还不能转向和播放相应动作,下节课会继续完善。

19. 完成玩家移动

接下来补充一下鼠标控制视角旋转以及角色转向相关的逻辑

SlAiPlayerCharacter.h

private:

	UPROPERTY(VisibleDefaultsOnly, Category = "SlAi")
	USkeletalMeshComponent* MeshFirst;

	// 旋转比例
	float BaseLookUpRate;
	float BaseTurnRate;

SlAiPlayerCharacter.cpp

// ... 省略
// 引入头文件
#include "GameFramework/CharacterMovementComponent.h"

ASlAiPlayerCharacter::ASlAiPlayerCharacter()
{
	// ... 省略
	
	// 默认第三人称
	FirstCamera->SetActive(false);
	ThirdCamera->SetActive(true);
	// 不显示第一人称模型
	GetMesh()->SetOwnerNoSee(false);
	MeshFirst->SetOwnerNoSee(true);

	// 初始化参数
	BaseTurnRate = 45.f;
	BaseLookUpRate = 45.f;
	// 设置初始速度为 150.f
	GetCharacterMovement()->MaxWalkSpeed = 150.f;
}

void ASlAiPlayerCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
	Super::SetupPlayerInputComponent(PlayerInputComponent);

	check(PlayerInputComponent);

	PlayerInputComponent->BindAxis("MoveForward", this, &ASlAiPlayerCharacter::MoveForward);
	PlayerInputComponent->BindAxis("MoveRight", this, &ASlAiPlayerCharacter::MoveRight);
	// 绑定转向
	PlayerInputComponent->BindAxis("Turn", this, &ASlAiPlayerCharacter::Turn);
	PlayerInputComponent->BindAxis("LookUp", this, &ASlAiPlayerCharacter::LookUpAtRate);
	PlayerInputComponent->BindAxis("TurnRate", this, &ASlAiPlayerCharacter::TurnAtRate);
	// 绑定动作相关的按键和方法,与上面的区别是它有按下按键和松开按键的状态判定
	PlayerInputComponent->BindAction("Jump", IE_Pressed, this, &ASlAiPlayerCharacter::OnStartJump);
	PlayerInputComponent->BindAction("Jump", IE_Released, this, &ASlAiPlayerCharacter::OnStopJump);
	PlayerInputComponent->BindAction("Run", IE_Pressed, this, &ASlAiPlayerCharacter::OnStartRun);
	PlayerInputComponent->BindAction("Run", IE_Released, this, &ASlAiPlayerCharacter::OnStopRun);
}

// ... 省略

void ASlAiPlayerCharacter::LookUpAtRate(float Value)
{
	AddControllerPitchInput(Value * BaseLookUpRate * GetWorld()->GetDeltaSeconds());
}

void ASlAiPlayerCharacter::Turn(float Value)
{
	AddControllerYawInput(Value);
}

void ASlAiPlayerCharacter::TurnAtRate(float Value)
{
	AddControllerYawInput(Value * BaseTurnRate * GetWorld()->GetDeltaSeconds());
}

void ASlAiPlayerCharacter::OnStartJump()
{
	bPressedJump = true;
}

void ASlAiPlayerCharacter::OnStopJump()
{
	bPressedJump = false;
	StopJumping();
}

void ASlAiPlayerCharacter::OnStartRun()
{
	GetCharacterMovement()->MaxWalkSpeed = 375.f;
}

void ASlAiPlayerCharacter::OnStopRun()
{
	GetCharacterMovement()->MaxWalkSpeed = 150.f;
}

为了让角色动作动画符合当前移动状态,需要让角色的运动和状态数据同步到动画类。

SlAiPlayerAnim.h

{
public:

	USlAiPlayerAnim();
	
	// 重写
	virtual void NativeUpdateAnimation(float DeltaSeconds) override;


protected:

	// 获取角色指针
	void InitSPCharacter();

	// 更新属性
	virtual void UpdateParameter();

protected:

	// 角色指针
	class ASlAiPlayerCharacter* SPCharacter;
}

SlAiPlayerAnim.cpp

// 引入头文件
#include "SlAiPlayerCharacter.h"

void USlAiPlayerAnim::NativeUpdateAnimation(float DeltaSeconds)
{
	// 初始化角色指针
	InitSPCharacter();
	// 更新蓝图数据
	UpdateParameter();
}

void USlAiPlayerAnim::InitSPCharacter()
{
	if (!SPCharacter) SPCharacter = Cast<ASlAiPlayerCharacter>(TryGetPawnOwner());
}

void USlAiPlayerAnim::UpdateParameter()
{
	// 如果不存在则直接返回,避免空指针产生中断
	if (!SPCharacter) return;
	// 设置速度
	Speed = SPCharacter->GetVelocity().Size();
}

SlAiThirdPlayerAnim.h

{
protected:

	// 重写更新属性方法
	virtual void UpdateParameter() override;
}

SlAiThirdPlayerAnim.cpp

#include "SlAiThirdPlayerAnim.h"
// 添加头文件
#include "SlAiPlayerCharacter.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "SlAiHelper.h"


void USlAiThirdPlayerAnim::UpdateParameter()
{
	Super::UpdateParameter();
	// 如果不存在则直接返回,避免空指针产生中断
	if (!SPCharacter) return;
	// 获取是否在跳跃
	IsInAir = SPCharacter->GetCharacterMovement()->IsFalling();
	// 速度朝向的 Yaw 旋转减去 Actor 朝向(摄像机朝向)的 Yaw 旋转得到相对的方向
	float PreDir = SPCharacter->GetVelocity().ToOrientationRotator().Yaw - SPCharacter->GetActorRotation().Yaw;

	// 输出 Debug 语句测试一下数据
	SlAiHelper::Debug(FString("SPCharacter->GetVelocity : ") + FString::SanitizeFloat(SPCharacter->GetVelocity().ToOrientationRotator().Yaw), 0.f);
	SlAiHelper::Debug(FString("SCharacter : ") + FString::SanitizeFloat(SPCharacter->GetActorRotation().Yaw), 0.f);
	SlAiHelper::Debug(FString("PreDir : ") + FString::SanitizeFloat(PreDir), 0.f);
	
	// 此处需要一些点乘/点积知识
	if (PreDir > 180.f) PreDir -= 360.f;
	if (PreDir < -180.f) PreDir += 360.f;
	Direction = PreDir;

	// 输出 Debug 语句测试一下数据
	SlAiHelper::Debug(FString("Direction : ") + FString::SanitizeFloat(Direction), 0.f);
}

(此处应该有动图,不过录了几遍都超过图片上传大小限制了,所以请读者自行脑补)

此时人物的移动控制和动画播放都能正常运作了,左上角显示的 Debug 内容也展示了人物的移动数据。关于获取 Direction 这一段如果没有搞懂的话建议补充下数学知识,多听两遍老师的讲解。如果后续不需要这些 Debug 内容的话记得去掉。

20. 视角切换与 Montage 播放

接下来添加视角切换和鼠标左右键的的输入按键绑定。

切换视角和鼠标左右
在数据结构类里面定义不同视角的状态

SlAiTypes.h

// 视角模式
namespace EGameViewMode {
	enum Type
	{
		First,
		Third
	};
}

在角色类里添加一下这个视角状态的变量与切换的方法。

SlAiPlayerCharacter.h

#include "GameFramework/Character.h"
// 添加头文件、
#include "SlAiTypes.h"

class SLAICOURSE_API ASlAiPlayerCharacter : public ACharacter
{
	GENERATED_BODY()
	
public:

	// 切换视角的方法
	void ChangeView(EGameViewMode::Type NewGameView);

public:

	// 当前的视角模式的枚举
	EGameViewMode::Type GameView;
}

SlAiPlayerCharacter.cpp

ASlAiPlayerCharacter::ASlAiPlayerCharacter()
{

	GetCharacterMovement()->MaxWalkSpeed = 150.f;
	// 初始为第三人称
	GameView = EGameViewMode::Third;
}


void ASlAiPlayerCharacter::ChangeView(EGameViewMode::Type NewGameView)
{
	GameView = NewGameView;
	switch (GameView)	// 其实就是控制下当前使用哪个摄像机,显示哪个网格
	{
	case EGameViewMode::First:
		FirstCamera->SetActive(true);
		ThirdCamera->SetActive(false);
		MeshFirst->SetOwnerNoSee(false);
		GetMesh()->SetOwnerNoSee(true);
		break;
	case EGameViewMode::Third:
		FirstCamera->SetActive(false);
		ThirdCamera->SetActive(true);
		MeshFirst->SetOwnerNoSee(true);
		GetMesh()->SetOwnerNoSee(false);
		break;
	}
}

随后在玩家控制器里绑定下切换视角的按键与方法,并在 BeginPlay() 里进行初始化操作。

SlAiPlayerController.h

{
public:

	ASlAiPlayerController();

	virtual void Tick(float DeltaSeconds) override;

	virtual void SetupInputComponent() override;

public:

	// 获取玩家角色
	class ASlAiPlayerCharacter* SPCharacter;

protected:

	virtual void BeginPlay() override;

private:

	// 切换视角
	void ChangeView();

	// 鼠标按键事件
	void LeftEventStart();
	void LeftEventStop();
	void RightEventStart();
	void RightEventStop();
}

SlAiPlayerController.cpp

// 添加头文件
#include "SlAiPlayerCharacter.h"

ASlAiPlayerController::ASlAiPlayerController()
{
	// 允许每帧运行
	PrimaryActorTick.bCanEverTick = true;
}

void ASlAiPlayerController::BeginPlay()
{
	Super::BeginPlay();
	// 获取角色
	if (!SPCharacter) SPCharacter = Cast<ASlAiPlayerCharacter>(GetCharacter());

	// 设置鼠标不显示
	bShowMouseCursor = false;
	// 设置输入模式
	FInputModeGameOnly InputMode;
	InputMode.SetConsumeCaptureMouseDown(true);
	SetInputMode(InputMode);
}

void ASlAiPlayerController::Tick(float DeltaSeconds)
{

}

void ASlAiPlayerController::SetupInputComponent()
{
	Super::SetupInputComponent();

	// 绑定视角切换
	InputComponent->BindAction("ChangeView", IE_Pressed, this, &ASlAiPlayerController::ChangeView);
	// 绑定鼠标按下事件
	InputComponent->BindAction("LeftEvent", IE_Pressed, this, &ASlAiPlayerController::LeftEventStart);
	InputComponent->BindAction("LeftEvent", IE_Released, this, &ASlAiPlayerController::LeftEventStop);
	InputComponent->BindAction("RightEvent", IE_Pressed, this, &ASlAiPlayerController::RightEventStart);
	InputComponent->BindAction("RightEvent", IE_Released, this, &ASlAiPlayerController::RightEventStop);
}

void ASlAiPlayerController::ChangeView()
{
	switch (SPCharacter->GameView)
	{
	case EGameViewMode::First:
		SPCharacter->ChangeView(EGameViewMode::Third);
		break;
	case EGameViewMode::Third:
		SPCharacter->ChangeView(EGameViewMode::First);
		break;
	}
}

void ASlAiPlayerController::LeftEventStart()
{
}

void ASlAiPlayerController::LeftEventStop()
{
}

void ASlAiPlayerController::RightEventStart()
{
}

void ASlAiPlayerController::RightEventStop()
{
}

现在运行后应该可以正常按 F 切换视角了。

Montage 播放

老师已经提前准备好了要用到的蒙太奇动画,其中的蒙太奇动画的具体设置不是课程的重点,大家只需要知道:蒙太奇动画中可以添加通知,使得动画播放到某一时刻时可以执行一些绑定好的操作。

分别在第一和第三人称的动画蓝图里设置好如下操作:将状态机动画缓存起来,然后通过选取上半身的 Slot 来专用于蒙太奇动画的播放,最后通过动画混合节点,让角色的上半身和下半身各自播放对应的动画。

第三人称动画蓝图:

第三人称
第一人称动画蓝图:

在这里插入图片描述

先在动画类里面指定蒙太奇动画的指针。

SlAiPlayerAnim.h

// 添加头文件
#include "Animation/AnimInstance.h"

class UAnimMontage;		// 提前声明

{


protected:

	void InitSPCharacter();

	virtual void UpdateParameter();

	// 更新动作
	virtual void UpdateMontage();

protected:

	class ASlAiPlayerCharacter* SPCharacter;

	// 上半身的 Montage
	UAnimMontage* PlayerHitMontage;
	UAnimMontage* PlayerFightMontage;
	UAnimMontage* PlayerPunchMontage;
	UAnimMontage* PlayerEatMontage;
	UAnimMontage* PlayerPickUpMontage;
}

SlAiPlayerAnim.cpp

// 引入头文件
#include "Animation/AnimMontage.h"

void USlAiPlayerAnim::NativeUpdateAnimation(float DeltaSeconds)
{

	InitSPCharacter();
	UpdateParameter();
	// 更新动作
	UpdateMontage();
}

void USlAiPlayerAnim::UpdateMontage()
{
	// 如果不存在则直接返回,避免空指针产生中断
	if (!SPCharacter) return;
	// 自动播放拳击动画,用于临时查看效果
	if (!Montage_IsPlaying(PlayerPunchMontage)) {
		Montage_Play(PlayerPunchMontage);
	}
}

给第三人称的动画类绑定蒙太奇动画资源。

SlAiThirdPlayerAnim.cpp

#include "SlAiThirdPlayerAnim.h"
#include "SlAiPlayerCharacter.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "SlAiHelper.h"
// 引入头文件
#include "Animation/AnimMontage.h"
#include "ConstructorHelpers.h"

USlAiThirdPlayerAnim::USlAiThirdPlayerAnim()
{
	// 绑定资源到 Montage
	static ConstructorHelpers::FObjectFinder<UAnimMontage> PlayerHitMon(TEXT
		("AnimMontage'/Game/Res/PolygonAdventure/Mannequin/Player/Animation/UpperBody/PlayerHitMontage.PlayerHitMontage'"));
	PlayerHitMontage = PlayerHitMon.Object;

	static ConstructorHelpers::FObjectFinder<UAnimMontage> PlayerEatMon(TEXT
		("AnimMontage'/Game/Res/PolygonAdventure/Mannequin/Player/Animation/UpperBody/PlayerEatMontage.PlayerEatMontage'"));
	PlayerEatMontage = PlayerEatMon.Object;

	static ConstructorHelpers::FObjectFinder<UAnimMontage> PlayerFightMon(TEXT
		("AnimMontage'/Game/Res/PolygonAdventure/Mannequin/Player/Animation/UpperBody/PlayerFightMontage.PlayerFightMontage'"));
	PlayerFightMontage = PlayerFightMon.Object;

	static ConstructorHelpers::FObjectFinder<UAnimMontage> PlayerPunchMon(TEXT
		("AnimMontage'/Game/Res/PolygonAdventure/Mannequin/Player/Animation/UpperBody/PlayerPunchMontage.PlayerPunchMontage'"));
	PlayerPunchMontage = PlayerPunchMon.Object;

	static ConstructorHelpers::FObjectFinder<UAnimMontage> PlayerPickUpMon(TEXT
		("AnimMontage'/Game/Res/PolygonAdventure/Mannequin/Player/Animation/UpperBody/PlayerPickUpMontage.PlayerPickUpMontage'"));
	PlayerPickUpMontage = PlayerPickUpMon.Object;

	Direction = 0.f;
	IsInAir = false;
}

void USlAiThirdPlayerAnim::UpdateParameter()
{
	Super::UpdateParameter();
	if (!SPCharacter) return;
	IsInAir = SPCharacter->GetCharacterMovement()->IsFalling();
	float PreDir = SPCharacter->GetVelocity().ToOrientationRotator().Yaw - SPCharacter->GetActorRotation().Yaw;
	
	if (PreDir > 180.f) PreDir -= 360.f;
	if (PreDir < -180.f) PreDir += 360.f;
	Direction = PreDir;

}

然后对第一人称的动画类也绑定蒙太奇动画资源。

SlAiFirstPlayerAnim.h

{
public:

	USlAiFirstPlayerAnim();
}

SlAiFirstPlayerAnim.cpp

// 添加头文件
#include "ConstructorHelpers.h"
#include "Animation/AnimMontage.h"

USlAiFirstPlayerAnim::USlAiFirstPlayerAnim()
{
	// 绑定资源到 Montage
	static ConstructorHelpers::FObjectFinder<UAnimMontage> PlayerHitMon(TEXT
	("AnimMontage'/Game/Res/PolygonAdventure/Mannequin/FirstPlayer/Animation/UpperBody/FirstPlayerHitMontage.FirstPlayerHitMontage'"));
	PlayerHitMontage = PlayerHitMon.Object;

	static ConstructorHelpers::FObjectFinder<UAnimMontage> PlayerEatMon(TEXT
	("AnimMontage'/Game/Res/PolygonAdventure/Mannequin/FirstPlayer/Animation/UpperBody/FirstPlayerEatMontage.FirstPlayerEatMontage'"));
	PlayerEatMontage = PlayerEatMon.Object;

	static ConstructorHelpers::FObjectFinder<UAnimMontage> PlayerFightMon(TEXT
	("AnimMontage'/Game/Res/PolygonAdventure/Mannequin/FirstPlayer/Animation/UpperBody/FirstPlayerFightMontage.FirstPlayerFightMontage'"));
	PlayerFightMontage = PlayerFightMon.Object;

	static ConstructorHelpers::FObjectFinder<UAnimMontage> PlayerPunchMon(TEXT
	("AnimMontage'/Game/Res/PolygonAdventure/Mannequin/FirstPlayer/Animation/UpperBody/FirstPlayerPunchMontage.FirstPlayerPunchMontage'"));
	PlayerPunchMontage = PlayerPunchMon.Object;

	static ConstructorHelpers::FObjectFinder<UAnimMontage> PlayerPickUpMon(TEXT
	("AnimMontage'/Game/Res/PolygonAdventure/Mannequin/FirstPlayer/Animation/UpperBody/FirstPlayerPickUpMontage.FirstPlayerPickUpMontage'"));
	PlayerPickUpMontage = PlayerPickUpMon.Object;
}

此时启动游戏,能看到角色上半身正在播放拳击动作,下半身可以跟随玩家的输入播放移动动画。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值