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;
}
此时启动游戏,能看到角色上半身正在播放拳击动作,下半身可以跟随玩家的输入播放移动动画。