斯坦福UE4 + C++课程学习记录 8:添加攻击动画

目录

1. 添加动画

2. 完整代码


1. 添加动画

        我们在之前的课程中实现了从角色右手发射魔法粒子,但角色并没有播放相应的动画,导致角色攻击看起来十分生硬。

图8-1 发射粒子无动画

        在我们引入的资产Gideon角色中,开发者已经做了充分的工作,使我们可以十分简单地调用函数来直接播放动画。要让骨骼体动起来,就需要使用动画序列,它是可在骨架网格体上播放的单个动画资源。这些序列包含各个关键帧,依次回放这些关键帧(相互合成)就可以实现骨骼体动画。而UE中控制动画播放的系统称为蒙太奇(Montage),它可以分别通过蓝图或C++来控制动画序列播放。

        在了解相关原理后,我们在SurCharacter.cpp的PrimaryAttack函数实现播放动画的功能。在.h和.cpp的相应位置分别添加如下代码:

// SurCharacter.cpp -> PrimaryAttack()
PlayAnimMontage(AttackAnim);

// SurCharacter.h
// Category指定在蓝图编辑工具中显示时的属性类别
UPROPERTY(EditAnywhere, Category = "Attack")
UAnimMontage* AttackAnim;

// 之前的投射体子类,也加上Category
UPROPERTY(EditAnywhere, Category = "Attack")
SubclassOf<AActor> ProjectileClass;

        然后在UE中给AttackAnim选择任意动画即可。需要注意的是,我们在使用UPROPERTY宏时加上Category,可以使相关的变量在蓝图编辑器中显示在Attack类别下。

图8-2 选择动画

        运行测试,可以发现动画可以正常播放,不过魔法粒子发射的位置还是角色在静止状态下右手的位置,而没有从角色抬手后的手掌中发出来。(虽然这看起来也算另一种很酷的效果)

图8-3 运行测试

        产生这个问题的原因,在于计算粒子起点的时候,即执行  FVector RightHandLoc = GetMesh()->GetSocketLocation("Muzzle_01");  这条语句时,角色才刚刚开始播放动画,得到的位置自然在下方。解决方法也很简单,根据动画的速度在这条语句前设置相应延迟即可,所以我们利用UE中的计时器来实现这个效果:

// 左键攻击
void ASurCharacter::PrimaryAttack() {

	PlayAnimMontage(AttackAnim);
    // 0.18s延迟后触发魔法粒子生成
	GetWorldTimerManager().SetTimer(TimerHandle_PrimaryAttack, this, &ASurCharacter::PrimaryAttack_TimeElapsed, 0.18f);

}
// 原先控制魔法粒子的内容移到这个函数中
void ASurCharacter::PrimaryAttack_TimeElapsed() {
	// 获取模型右手位置
	FVector RightHandLoc = GetMesh()->GetSocketLocation("Muzzle_01");

	// 朝向角色方向,在角色的右手位置生成
	FTransform SpawnTM = FTransform(GetActorRotation(), RightHandLoc);

	// 此处设置碰撞检测规则为:即使碰撞也总是生成
	FActorSpawnParameters SpawnParams;
	SpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn;

	GetWorld()->SpawnActor<AActor>(ProjectileClass, SpawnTM, SpawnParams);
}

        要注意传入的定时器句柄TimerHandle_PrimaryAttack是FTimerHandle类,需要在SurCharacter.h文件中声明。只要定时器句柄存在,过0.2s后就会执行PrimaryAttack_TimeElapsed()中发射粒子的代码。这样做的好处是,当角色在攻击的一瞬间死亡时,其TimerHandle_PrimaryAttack将会自动取消,那之后的动画也就不会再播放。

        现在运行代码,可以发现粒子发射的位置成功更换到了Gideon的手上。

图8-4 运行测试

         进一步,如果想要取得更精细的调整结果,你可以尝试把0.18f这个值定义成变量并暴露在UE中,直接在UE中边运行边调整。


2. 完整代码

SurCharacter.h

#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "SurCharacter.generated.h"

class USpringArmComponent;
class UCameraComponent;
class USurInteractionComponent;
class UAnimMontage;

UCLASS()
class SURKEAUE_API ASurCharacter : public ACharacter
{
	GENERATED_BODY()

public:

	ASurCharacter();

protected:

	//弹簧臂组件
	UPROPERTY(VisibleAnywhere)
	USpringArmComponent* SpringArmComp;
	//相机组件
	UPROPERTY(VisibleAnywhere)
	UCameraComponent* CameraComp;
	// 投射体子类
	UPROPERTY(EditAnywhere, Category = "Attack")
	TSubclassOf<AActor> ProjectileClass;
	// 界面
	UPROPERTY(VisibleAnywhere)
	USurInteractionComponent* InteractionComp;

	virtual void BeginPlay() override;

	void MoveForward(float value);
	void MoveRight(float value);

	void PrimaryAttack();
	void PrimaryInteract();
	void PrimaryAttack_TimeElapsed();

	// 动画
	UPROPERTY(EditAnywhere, Category = "Attack")
	UAnimMontage* AttackAnim;

	FTimerHandle TimerHandle_PrimaryAttack;

public:

	virtual void Tick(float DeltaTime) override;

	virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;

};

SurCharacter.cpp

#include "SurCharacter.h"
#include "GameFramework/SpringArmComponent.h"
#include "Camera/CameraComponent.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "SurInteractionComponent.h"


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

	SpringArmComp = CreateDefaultSubobject<USpringArmComponent>("SpringArmComp");
	SpringArmComp->bUsePawnControlRotation = true;
	SpringArmComp->SetupAttachment(RootComponent);

	CameraComp = CreateDefaultSubobject<UCameraComponent>("CameraComp");
	CameraComp->SetupAttachment(SpringArmComp);

	InteractionComp = CreateDefaultSubobject<USurInteractionComponent>("InteractionComp");

	GetCharacterMovement()->bOrientRotationToMovement = true;
	bUseControllerRotationYaw = false;
}

void ASurCharacter::BeginPlay()
{
	Super::BeginPlay();
}
void ASurCharacter::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);
}

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

	// 移动控制
	PlayerInputComponent->BindAxis("MoveForward", this, &ASurCharacter::MoveForward);
	PlayerInputComponent->BindAxis("MoveRight", this, &ASurCharacter::MoveRight);
	PlayerInputComponent->BindAxis("Turn", this, &APawn::AddControllerYawInput);
	PlayerInputComponent->BindAxis("LookUp", this, &APawn::AddControllerPitchInput);
	// 跳跃
	PlayerInputComponent->BindAction("Jump", IE_Pressed, this, &ACharacter::Jump);
	// 攻击
	PlayerInputComponent->BindAction("PrimaryAttack", IE_Pressed, this, &ASurCharacter::PrimaryAttack);
	// 交互
	PlayerInputComponent->BindAction("PrimaryInteract", IE_Pressed, this, &ASurCharacter::PrimaryInteract);
}

// 角色向前移动
void ASurCharacter::MoveForward(float value)
{
	FRotator ControlRot = GetControlRotation();
	// 转向只关注水平Yaw方向,因此置0防止影响
	ControlRot.Pitch = 0;
	ControlRot.Roll = 0;
	// 获取相机(鼠标控制器)的朝向,并朝这个方向移动
	AddMovementInput(ControlRot.Vector(), value);
}

// 角色向右移动
void ASurCharacter::MoveRight(float value)
{
	FRotator ControlRot = GetControlRotation();
	ControlRot.Pitch = 0;
	ControlRot.Roll = 0;
	// 获取相机(鼠标控制器)的朝向,转向右侧,并朝这个方向移动
	FVector RightVector = FRotationMatrix(ControlRot).GetScaledAxis(EAxis::Y);
	AddMovementInput(RightVector, value);
}

// 左键攻击
void ASurCharacter::PrimaryAttack() {

	PlayAnimMontage(AttackAnim);

	GetWorldTimerManager().SetTimer(TimerHandle_PrimaryAttack, this, &ASurCharacter::PrimaryAttack_TimeElapsed, 0.18f);

}

void ASurCharacter::PrimaryAttack_TimeElapsed() {
	// 获取模型右手位置
	FVector RightHandLoc = GetMesh()->GetSocketLocation("Muzzle_01");

	// 朝向角色方向,在角色的右手位置生成
	FTransform SpawnTM = FTransform(GetActorRotation(), RightHandLoc);

	// 此处设置碰撞检测规则为:即使碰撞也总是生成
	FActorSpawnParameters SpawnParams;
	SpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn;

	GetWorld()->SpawnActor<AActor>(ProjectileClass, SpawnTM, SpawnParams);
}

// 交互
void ASurCharacter::PrimaryInteract() {

	InteractionComp->PrimaryInteract();
}

猜你喜欢

转载自blog.csdn.net/surkea/article/details/127135142