UEC++ day8

伤害系统

给敌人创建血条

  • 首先添加一个UI界面用来显示敌人血条
  • 设置背景图像为黑色半透明
  • 填充颜色
  • 给敌人类添加两种状态表示血量与最大血量,添加一个UWidegtComponet组件与UProgressBar组件
cpp 复制代码
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Enemy Stats")
	float Health;
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Enemy Stats")
	float MaxHealth;
	UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "Enemy Stats")
	class UWidgetComponent* HealthBarWidgetComponent;
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Enemy Stats")
	class UProgressBar* HealthBar;


MaxHealth = 100.f;
Health = MaxHealth;

设置WidgetComponent组件

  • 头文件:
    • #include "Components/WidgetComponent.h":使用WidgetComponent就要使用
    • #include "Blueprint/UserWidget.h":获取到User内部对象时需要使用
    • #include "Components/ProgressBar.h":获取调用ProgressBar需要使用
  • 创建UWidgetComponent组件
cpp 复制代码
	HealthBarWidgetComponent = CreateDefaultSubobject<UWidgetComponent>(TEXT("HealthBarWidgetComponent"));
	HealthBarWidgetComponent->SetupAttachment(GetRootComponent());
	HealthBarWidgetComponent->SetWidgetSpace(EWidgetSpace::Screen);
	HealthBarWidgetComponent->SetDrawSize(FVector2D(125.f, 10.f));
	HealthBarWidgetComponent->SetWorldLocation(FVector(0.f, 0.f, 50.f));
  • 获取到HealBar小组件
cpp 复制代码
// Called when the game starts or when spawned
void ABaseEnemy::BeginPlay()
{
	Super::BeginPlay();

	ChaseVolume->OnComponentBeginOverlap.AddDynamic(this, &ABaseEnemy::OnChaseVolumeOverlapBegin);
	ChaseVolume->OnComponentEndOverlap.AddDynamic(this, &ABaseEnemy::OnChaseVolumeOverlapEnd);

	AttackVolume->OnComponentBeginOverlap.AddDynamic(this, &ABaseEnemy::OnAttackVolumeOverlapBegin);
	AttackVolume->OnComponentEndOverlap.AddDynamic(this, &ABaseEnemy::OnAttackVolumeOverlapEnd);
	
	//获取到HealBar小组件
	HealthBar = Cast<UProgressBar>(HealthBarWidgetComponent->GetUserWidgetObject()->GetWidgetFromName("HealthBar"));
	HealthBar->SetPercent(Health / MaxHealth);

	//拿到Controller
	AIController = Cast<AAIController>(GetController());
}
  • 运行结果

血条进入敌人追逐区显示

  • 一开始敌人血条是不显示的,只有主角进入了敌人追逐区才开始显示血条,出了追逐区也不显示
cpp 复制代码
	//获取到HealBar小组件
	HealthBar = Cast<UProgressBar>(HealthBarWidgetComponent->GetUserWidgetObject()->GetWidgetFromName("HealthBar"));
	HealthBar->SetPercent(Health / MaxHealth);
	HealthBar->SetVisibility(ESlateVisibility::Hidden);//一开始不显示血条


void ABaseEnemy::OnChaseVolumeOverlapBegin(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
	if (OtherActor)
	{
		AMainPlayer* Player = Cast<AMainPlayer>(OtherActor);
		if (Player)
		{
			//主角进入追逐范围显示血条
			HealthBar->SetVisibility(ESlateVisibility::Visible);
			MoveToTarget(Player);
		}
	}
}

void ABaseEnemy::OnChaseVolumeOverlapEnd(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex)
{
	if (OtherActor)
	{
		AMainPlayer* Player = Cast<AMainPlayer>(OtherActor);
		if (Player)
		{
			if (AIController)
			{
				//主角出追逐范围不显示血条
				HealthBar->SetVisibility(ESlateVisibility::Hidden);
				//停止移动
				AIController->StopMovement();
			}
		}
	}
}

触发伤害思路

  • 我们可以在武器上加一个碰撞器,当这个碰撞器碰撞到敌人的时候就开启伤害,如果没有就躲避的伤害
  • 给每把剑的骨骼添加一个Socket

触发伤害需求

  • 因为要使用UE中内置的直接伤害附加的功能,类似爆炸物那一节,所以我们要在武器类中新建一个盒子碰撞触发器,新建一个伤害值,新建一个伤害类型,新建一个伤害发起者
cpp 复制代码
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Weapon|Attack")
	class UBoxComponent* AttackCollision;

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Weapon|Attack")
	float Damage;

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Weapon|Attack")
	TSubclassOf<UDamageType> DamageTyClass;

	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Weapon|Attack")
	class AController* WeaponOwner;
  • 新建伤害开始触发事件与结束事件,两个动态切换碰撞函数,我只需要在挥剑的那个瞬间去切换需要在动画的notify中去切换的所以需要添加反射
cpp 复制代码
	UFUNCTION()
	void OnAttackCollisionOverlapBegin(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult);
	UFUNCTION()
	void OnAttackCollisionOverlapEnd(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex);
	//动态切换碰撞
	UFUNCTION(BlueprintCallable)
	void ActiveAttackCollision();
	UFUNCTION(BlueprintCallable)
	void DeactiveAttackCollision();
  • 初始化,创建BoxComponent需要头文件:#include "Components/BoxComponent.h"
cpp 复制代码
	AttackCollision = CreateDefaultSubobject<UBoxComponent>(TEXT("AttackCollision"));
	AttackCollision->SetupAttachment(DisplayMesh, "WeaponSocket");//将这个碰撞点附加到武器插槽上
	DeactiveAttackCollision();//关闭碰撞

Damage = 25.f;

//绑定
void AWeaponItem::BeginPlay()
{
	Super::BeginPlay();
	AttackCollision->OnComponentBeginOverlap.AddDynamic(this, &AWeaponItem::OnAttackCollisionOverlapBegin);
	AttackCollision->OnComponentEndOverlap.AddDynamic(this, &AWeaponItem::OnAttackCollisionOverlapEnd);
}

void AWeaponItem::OnAttackCollisionOverlapBegin(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
}

void AWeaponItem::OnAttackCollisionOverlapEnd(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex)
{
}
//设置碰撞类型
void AWeaponItem::ActiveAttackCollision()
{
	AttackCollision->SetCollisionEnabled(ECollisionEnabled::QueryOnly);
	AttackCollision->SetCollisionObjectType(ECollisionChannel::ECC_WorldDynamic);
	AttackCollision->SetCollisionResponseToAllChannels(ECollisionResponse::ECR_Ignore);
	AttackCollision->SetCollisionResponseToChannel(ECollisionChannel::ECC_Pawn, ECollisionResponse::ECR_Overlap);

}

void AWeaponItem::DeactiveAttackCollision()
{
	AttackCollision->SetCollisionEnabled(ECollisionEnabled::NoCollision);
}
WeaponItem.cpp
cpp 复制代码
// Fill out your copyright notice in the Description page of Project Settings.
#include "WeaponItem.h"
#include "Components/SkeletalMeshComponent.h"
#include "Characters/Player/MainPlayer.h"
#include "Engine/SkeletalMeshSocket.h"
#include "Kismet/GameplayStatics.h"
#include "Sound/SoundCue.h"
#include "Particles/ParticleSystemComponent.h"
#include "UObject/ConstructorHelpers.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "Components/BoxComponent.h"
AWeaponItem::AWeaponItem()
{
	//销毁
	if (DisplayMesh)
	{
		DisplayMesh->DestroyComponent();
		//UE_LOG(LogTemp, Warning, TEXT("delete succeed"));
	}
	else
	{
		//UE_LOG(LogTemp, Warning, TEXT("fail to delete"));
	}
	
	//因为TEXT具有唯一性,我们不知道什么时候销毁原UStaticMeshComponent* DisplayMesh;所以这里TEXT进行一下区分
	DisplayMesh=CreateDefaultSubobject<USkeletalMeshComponent>(TEXT("DisplaySkeletalMesh"));
	DisplayMesh->SetupAttachment(GetRootComponent());
	ActiveDisplayMeshCollision();//设置碰撞

	AttackCollision = CreateDefaultSubobject<UBoxComponent>(TEXT("AttackCollision"));
	AttackCollision->SetupAttachment(DisplayMesh, "WeaponSocket");//将这个碰撞点附加到武器插槽上
	DeactiveAttackCollision();

	static ConstructorHelpers::FObjectFinder<USoundCue> SoundCueAsset(TEXT("SoundCue'/Game/Assets/Audios/Blade_Cue.Blade_Cue'"));
	if (SoundCueAsset.Succeeded())
	{
		OnEquipSound = SoundCueAsset.Object;
	}

	//拾取武器后粒子效果默认关闭
	bOnEquipParticle = false;
	//默认状态武器是可拾取的
	WeaponState = EWeaponState::EWS_CanPickUp;
	
	Damage = 25.f;
}

void AWeaponItem::BeginPlay()
{
	Super::BeginPlay();
	AttackCollision->OnComponentBeginOverlap.AddDynamic(this, &AWeaponItem::OnAttackCollisionOverlapBegin);
	AttackCollision->OnComponentEndOverlap.AddDynamic(this, &AWeaponItem::OnAttackCollisionOverlapEnd);
}

void AWeaponItem::OnOverlapBegin(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
	Super::OnOverlapBegin(OverlappedComponent, OtherActor, OtherComp, OtherBodyIndex, bFromSweep, SweepResult);
	
	if (OtherActor && WeaponState == EWeaponState::EWS_CanPickUp)
	{
		AMainPlayer* Player = Cast<AMainPlayer>(OtherActor);
		if (Player)
		{
			//告诉角色正在重叠的武器是当前武器
			Player->OverlapWeapon = this;
		}
	}
}

void AWeaponItem::OnOverlapEnd(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex)
{
	Super::OnOverlapEnd(OverlappedComponent, OtherActor, OtherComp, OtherBodyIndex);
	
	if (OtherActor)
	{
		AMainPlayer* Player = Cast<AMainPlayer>(OtherActor);
		//判断一开始是否拾起的武器是当前武器
		if (Player && Player->OverlapWeapon == this)
		{
			//告诉角色离开了武器触发器
			Player->OverlapWeapon = nullptr;
		}
	}
}

void AWeaponItem::Equip(AMainPlayer* Player)
{
	if (Player && !Player->GetMovementComponent()->IsFalling())
	{
		//已装备武器
		WeaponState = EWeaponState::EWS_Equip;
		DeactiveDisplayMeshCollision();//关闭碰撞
		//获取Player的Socket
		const USkeletalMeshSocket* RightHandSocker = Player->GetMesh()->GetSocketByName(TEXT("RightHandSocket"));
		if (RightHandSocker)
		{
			//让武器附属到Socket上
			RightHandSocker->AttachActor(this, Player->GetMesh());
			Player->bIsWeapon = true;
			Player->EquipWeapon = this;
			Player->OverlapWeapon = nullptr;
			bRotate = false;//武器旋转关闭

			if (OnEquipSound)
			{
				//播放音乐
				UGameplayStatics::PlaySound2D(this, OnEquipSound);
			}
			//if (!bOnEquipParticle)
			//{
			//	//关闭粒子组件
			//	ParticleEffectsComponent->Deactivate();
			//	
			//}
			
		}
	}
}

void AWeaponItem::UnEuip(AMainPlayer* Player)
{
	if (Player && !Player->GetMovementComponent()->IsFalling() && !Player->bIsAttacking)
	{
		WeaponState = EWeaponState::EWS_CanPickUp;
		ActiveDisplayMeshCollision();//开启碰撞
		Player->bIsWeapon = false;
		Player->EquipWeapon = nullptr;
		if (Player->OverlapWeapon == nullptr)
		{
			Player->OverlapWeapon = this;
		}

		//分离当前WeaponItem Socket
		DetachFromActor(FDetachmentTransformRules::KeepWorldTransform);
		SetActorRotation(FRotator(0.f));
		SetActorScale3D(FVector(1.f));
		bRotate = true;	
	}
}

void AWeaponItem::ActiveDisplayMeshCollision()
{
	DisplayMesh->SetCollisionEnabled(ECollisionEnabled::PhysicsOnly);
	DisplayMesh->SetCollisionObjectType(ECollisionChannel::ECC_WorldStatic);
	DisplayMesh->SetCollisionResponseToAllChannels(ECollisionResponse::ECR_Ignore);
	DisplayMesh->SetCollisionResponseToChannel(ECollisionChannel::ECC_Pawn, ECollisionResponse::ECR_Block);

}

void AWeaponItem::DeactiveDisplayMeshCollision()
{
	DisplayMesh->SetCollisionEnabled(ECollisionEnabled::NoCollision);
}

void AWeaponItem::OnAttackCollisionOverlapBegin(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
}

void AWeaponItem::OnAttackCollisionOverlapEnd(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex)
{
}

void AWeaponItem::ActiveAttackCollision()
{
	AttackCollision->SetCollisionEnabled(ECollisionEnabled::QueryOnly);
	AttackCollision->SetCollisionObjectType(ECollisionChannel::ECC_WorldDynamic);
	AttackCollision->SetCollisionResponseToAllChannels(ECollisionResponse::ECR_Ignore);
	AttackCollision->SetCollisionResponseToChannel(ECollisionChannel::ECC_Pawn, ECollisionResponse::ECR_Overlap);

}

void AWeaponItem::DeactiveAttackCollision()
{
	AttackCollision->SetCollisionEnabled(ECollisionEnabled::NoCollision);
}

在蒙太奇中添加攻击通知激活伤害碰撞

  • 在主角的蒙太奇中添加四个通知,跟当时添加攻击结束通知差不多
  • 然后在动画蓝图中进行调用函数
  • 将三把剑的盒子碰撞检测大小调整一下


给敌人添加触发器

  • 基本与上面给主角添加伤害需求的思路一致
  • 先给敌人添加两个骨骼

触发伤害需求

  • 再来就是编辑敌人类的变量需求与基本逻辑了,两个触发盒子因为是两只腿的攻击,新建伤害值,伤害类型
cpp 复制代码
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Attack")
	class UBoxComponent* LeftAttackCollision;
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Attack")
	UBoxComponent* RightAttackCollision;
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Attack")
	float Damage;
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Attack")
	TSubclassOf<UDamageType> DamageTyClass;
  • 不用说,现在是四个伤害触发事件的新建,四个动态切换碰撞的函数
cpp 复制代码
	UFUNCTION()
	void OnLeftAttackCollisionOverlapBegin(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult);
	UFUNCTION()
	void OnLeftAttackCollisionOverlapEnd(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex);
	UFUNCTION()
	void OnRightAttackCollisionOverlapBegin(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult);
	UFUNCTION()
	void OnRightAttackCollisionOverlapEnd(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex);
	//动态切换碰撞
	UFUNCTION(BlueprintCallable)
	void ActiveLeftAttackCollision();
	UFUNCTION(BlueprintCallable)
	void DeactiveLeftAttackCollision();
	UFUNCTION(BlueprintCallable)
	void ActiveRightAttackCollision();
	UFUNCTION(BlueprintCallable)
	void DeactiveRightAttackCollision();
  • 现在就可以开始编写,初始化了
cpp 复制代码
	LeftAttackCollision = CreateDefaultSubobject<UBoxComponent>(TEXT("LeftAttackCollision"));
	LeftAttackCollision->SetupAttachment(GetMesh(), "LeftAttackSocket");
	DeactiveLeftAttackCollision();

	RightAttackCollision = CreateDefaultSubobject<UBoxComponent>(TEXT("RightAttackCollision"));
	RightAttackCollision->SetupAttachment(GetMesh(), "RightAttackSocket");
	DeactiveRightAttackCollision();

//赋值	
Damage = 10.f;

//绑定
	LeftAttackCollision->OnComponentBeginOverlap.AddDynamic(this, &ABaseEnemy::OnLeftAttackCollisionOverlapBegin);
	LeftAttackCollision->OnComponentEndOverlap.AddDynamic(this, &ABaseEnemy::OnLeftAttackCollisionOverlapEnd);

	RightAttackCollision->OnComponentBeginOverlap.AddDynamic(this, &ABaseEnemy::OnRightAttackCollisionOverlapBegin);
	RightAttackCollision->OnComponentEndOverlap.AddDynamic(this, &ABaseEnemy::OnRightAttackCollisionOverlapEnd);

void ABaseEnemy::ActiveLeftAttackCollision()
{
	LeftAttackCollision->SetCollisionEnabled(ECollisionEnabled::QueryOnly);
	LeftAttackCollision->SetCollisionObjectType(ECollisionChannel::ECC_WorldDynamic);
	LeftAttackCollision->SetCollisionResponseToAllChannels(ECollisionResponse::ECR_Ignore);
	LeftAttackCollision->SetCollisionResponseToChannel(ECollisionChannel::ECC_Pawn, ECollisionResponse::ECR_Overlap);
}

void ABaseEnemy::DeactiveLeftAttackCollision()
{
	LeftAttackCollision->SetCollisionEnabled(ECollisionEnabled::NoCollision);
}

void ABaseEnemy::ActiveRightAttackCollision()
{
	RightAttackCollision->SetCollisionEnabled(ECollisionEnabled::QueryOnly);
	RightAttackCollision->SetCollisionObjectType(ECollisionChannel::ECC_WorldDynamic);
	RightAttackCollision->SetCollisionResponseToAllChannels(ECollisionResponse::ECR_Ignore);
	RightAttackCollision->SetCollisionResponseToChannel(ECollisionChannel::ECC_Pawn, ECollisionResponse::ECR_Overlap);
}

void ABaseEnemy::DeactiveRightAttackCollision()
{
	RightAttackCollision->SetCollisionEnabled(ECollisionEnabled::NoCollision);
}
BaseEnemy.cpp
cpp 复制代码
// Fill out your copyright notice in the Description page of Project Settings.


#include "BaseEnemy.h"
#include "Components/SphereComponent.h"
#include "Components/SkeletalMeshComponent.h"
#include "Components/CapsuleComponent.h"
#include "AIController.h"
#include "Characters/Player/MainPlayer.h"
#include "Animation/AnimInstance.h"
#include "Kismet/KismetMathLibrary.h"
#include "Kismet/GameplayStatics.h"
#include "Components/WidgetComponent.h"
#include "Blueprint/UserWidget.h"
#include "Components/ProgressBar.h"
#include "Components/BoxComponent.h"
// Sets default values
ABaseEnemy::ABaseEnemy()
{
 	// Set this character to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
	PrimaryActorTick.bCanEverTick = true;
	
	ChaseVolume = CreateDefaultSubobject<USphereComponent>(TEXT("ChaseVolume"));
	ChaseVolume->SetupAttachment(GetRootComponent());
	ChaseVolume->InitSphereRadius(800.f);
	ChaseVolume->SetCollisionObjectType(ECollisionChannel::ECC_WorldDynamic);
	ChaseVolume->SetCollisionResponseToAllChannels(ECollisionResponse::ECR_Ignore);
	ChaseVolume->SetCollisionResponseToChannel(ECollisionChannel::ECC_Pawn, ECollisionResponse::ECR_Overlap);

	AttackVolume = CreateDefaultSubobject<USphereComponent>(TEXT("AttackVolume"));
	AttackVolume->SetupAttachment(GetRootComponent());
	AttackVolume->InitSphereRadius(100.f);
	AttackVolume->SetCollisionObjectType(ECollisionChannel::ECC_WorldDynamic);
	AttackVolume->SetCollisionResponseToAllChannels(ECollisionResponse::ECR_Ignore);
	AttackVolume->SetCollisionResponseToChannel(ECollisionChannel::ECC_Pawn, ECollisionResponse::ECR_Overlap);
	
	HealthBarWidgetComponent = CreateDefaultSubobject<UWidgetComponent>(TEXT("HealthBarWidgetComponent"));
	HealthBarWidgetComponent->SetupAttachment(GetRootComponent());
	HealthBarWidgetComponent->SetWidgetSpace(EWidgetSpace::Screen);
	HealthBarWidgetComponent->SetDrawSize(FVector2D(125.f, 10.f));
	HealthBarWidgetComponent->SetWorldLocation(FVector(0.f, 0.f, 50.f));

	LeftAttackCollision = CreateDefaultSubobject<UBoxComponent>(TEXT("LeftAttackCollision"));
	LeftAttackCollision->SetupAttachment(GetMesh(), "LeftAttackSocket");
	DeactiveLeftAttackCollision();

	RightAttackCollision = CreateDefaultSubobject<UBoxComponent>(TEXT("RightAttackCollision"));
	RightAttackCollision->SetupAttachment(GetMesh(), "RightAttackSocket");
	DeactiveRightAttackCollision();

	//避免摄像机被敌人给阻挡
	GetMesh()->SetCollisionResponseToChannel(ECollisionChannel::ECC_Camera, ECollisionResponse::ECR_Ignore);
	GetCapsuleComponent()->SetCollisionResponseToChannel(ECollisionChannel::ECC_Camera, ECollisionResponse::ECR_Ignore);
	//设置持有属性
	AutoPossessAI = EAutoPossessAI::PlacedInWorldOrSpawned;

	//初始化默认移动状态
	EnemyMovementStatus = EEnemyMovementStatus::EEMS_Idle;

	InterpSpeed = 15.f;
	bInterpToPlayer = false;

	MaxHealth = 100.f;
	Health = MaxHealth;

	Damage = 10.f;
}

// Called when the game starts or when spawned
void ABaseEnemy::BeginPlay()
{
	Super::BeginPlay();

	ChaseVolume->OnComponentBeginOverlap.AddDynamic(this, &ABaseEnemy::OnChaseVolumeOverlapBegin);
	ChaseVolume->OnComponentEndOverlap.AddDynamic(this, &ABaseEnemy::OnChaseVolumeOverlapEnd);

	AttackVolume->OnComponentBeginOverlap.AddDynamic(this, &ABaseEnemy::OnAttackVolumeOverlapBegin);
	AttackVolume->OnComponentEndOverlap.AddDynamic(this, &ABaseEnemy::OnAttackVolumeOverlapEnd);

	LeftAttackCollision->OnComponentBeginOverlap.AddDynamic(this, &ABaseEnemy::OnLeftAttackCollisionOverlapBegin);
	LeftAttackCollision->OnComponentEndOverlap.AddDynamic(this, &ABaseEnemy::OnLeftAttackCollisionOverlapEnd);

	RightAttackCollision->OnComponentBeginOverlap.AddDynamic(this, &ABaseEnemy::OnRightAttackCollisionOverlapBegin);
	RightAttackCollision->OnComponentEndOverlap.AddDynamic(this, &ABaseEnemy::OnRightAttackCollisionOverlapEnd);
	
	//获取到HealBar小组件
	HealthBar = Cast<UProgressBar>(HealthBarWidgetComponent->GetUserWidgetObject()->GetWidgetFromName("HealthBar"));
	HealthBar->SetPercent(Health / MaxHealth);
	HealthBar->SetVisibility(ESlateVisibility::Hidden);//一开始不显示血条

	//拿到Controller
	AIController = Cast<AAIController>(GetController());
}

// Called every frame
void ABaseEnemy::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);

	if (bInterpToPlayer)
	{
		FRotator LookYaw(0.f, UKismetMathLibrary::FindLookAtRotation(GetActorLocation(), UGameplayStatics::GetPlayerPawn(this, 0)->GetActorLocation()).Yaw, 0.f);
		FRotator InterpRotation = FMath::RInterpTo(GetActorRotation(), LookYaw, DeltaTime, InterpSpeed);
		SetActorRotation(InterpRotation);
	}
}

// Called to bind functionality to input
void ABaseEnemy::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
	Super::SetupPlayerInputComponent(PlayerInputComponent);

}

void ABaseEnemy::OnChaseVolumeOverlapBegin(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
	if (OtherActor)
	{
		AMainPlayer* Player = Cast<AMainPlayer>(OtherActor);
		if (Player)
		{
			//主角进入追逐范围显示血条
			HealthBar->SetVisibility(ESlateVisibility::Visible);
			MoveToTarget(Player);
		}
	}
}

void ABaseEnemy::OnChaseVolumeOverlapEnd(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex)
{
	if (OtherActor)
	{
		AMainPlayer* Player = Cast<AMainPlayer>(OtherActor);
		if (Player)
		{
			if (AIController)
			{
				//主角出追逐范围不显示血条
				HealthBar->SetVisibility(ESlateVisibility::Hidden);
				//停止移动
				AIController->StopMovement();
			}
		}
	}
}

void ABaseEnemy::OnAttackVolumeOverlapBegin(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
	if (OtherActor)
	{
		AMainPlayer* Player = Cast<AMainPlayer>(OtherActor);
		if (Player)
		{
			Player->UpdataAttackTarget();
			bAttackVolumeOverlap = true;
			AttackBegin();
		}
	}
}

void ABaseEnemy::OnAttackVolumeOverlapEnd(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex)
{
	if (OtherActor)
	{
		AMainPlayer* Player = Cast<AMainPlayer>(OtherActor);
		if (Player)
		{
			bAttackVolumeOverlap = false;
			if (EnemyMovementStatus!=EEnemyMovementStatus::EEMS_Attacking)
			{
				MoveToTarget(Player);
			}
		}
	}
}

void ABaseEnemy::MoveToTarget(AMainPlayer* Player)
{
	EnemyMovementStatus = EEnemyMovementStatus::EEMS_MoveToTarget;
	if (AIController)
	{
		FAIMoveRequest MoveRequest;
		MoveRequest.SetGoalActor(Player);//设置移动请求目标
		MoveRequest.SetAcceptanceRadius(10.f);	//设置移动半径

		FNavPathSharedPtr NavPath;//会返回路径

		AIController->MoveTo(MoveRequest, &NavPath);
	}
}

void ABaseEnemy::AttackBegin()
{
	//攻击中关闭移动
	if (AIController)
	{
		AIController->StopMovement();
	}
	if (EnemyMovementStatus != EEnemyMovementStatus::EEMS_Attacking)
	{
		EnemyMovementStatus = EEnemyMovementStatus::EEMS_Attacking;

		bInterpToPlayer = true;

		UAnimInstance* AnimInstance = GetMesh()->GetAnimInstance();
		if (AnimInstance && AttackMontage)
		{
			float PlayRate = FMath::RandRange(0.9f, 1.1f);
			FString SectionName = FString::FromInt(FMath::RandRange(1, 3));
			AnimInstance->Montage_Play(AttackMontage, PlayRate);
			AnimInstance->Montage_JumpToSection(FName(*SectionName), AttackMontage);
		}
	}
}

void ABaseEnemy::AttackEnd()
{
	EnemyMovementStatus = EEnemyMovementStatus::EEMS_Idle;
	bInterpToPlayer = false;
	if (bAttackVolumeOverlap)
	{
		AttackBegin();
	}
}

void ABaseEnemy::OnLeftAttackCollisionOverlapBegin(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
}

void ABaseEnemy::OnLeftAttackCollisionOverlapEnd(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex)
{
}

void ABaseEnemy::OnRightAttackCollisionOverlapBegin(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
}

void ABaseEnemy::OnRightAttackCollisionOverlapEnd(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex)
{
}

void ABaseEnemy::ActiveLeftAttackCollision()
{
	LeftAttackCollision->SetCollisionEnabled(ECollisionEnabled::QueryOnly);
	LeftAttackCollision->SetCollisionObjectType(ECollisionChannel::ECC_WorldDynamic);
	LeftAttackCollision->SetCollisionResponseToAllChannels(ECollisionResponse::ECR_Ignore);
	LeftAttackCollision->SetCollisionResponseToChannel(ECollisionChannel::ECC_Pawn, ECollisionResponse::ECR_Overlap);
}

void ABaseEnemy::DeactiveLeftAttackCollision()
{
	LeftAttackCollision->SetCollisionEnabled(ECollisionEnabled::NoCollision);
}

void ABaseEnemy::ActiveRightAttackCollision()
{
	RightAttackCollision->SetCollisionEnabled(ECollisionEnabled::QueryOnly);
	RightAttackCollision->SetCollisionObjectType(ECollisionChannel::ECC_WorldDynamic);
	RightAttackCollision->SetCollisionResponseToAllChannels(ECollisionResponse::ECR_Ignore);
	RightAttackCollision->SetCollisionResponseToChannel(ECollisionChannel::ECC_Pawn, ECollisionResponse::ECR_Overlap);
}

void ABaseEnemy::DeactiveRightAttackCollision()
{
	RightAttackCollision->SetCollisionEnabled(ECollisionEnabled::NoCollision);
}

在蒙太奇中添加攻击通知激活伤害碰撞

  • 首先修改攻击触发器的大小
  • 在敌人的蒙太奇中添加通知,跟上面主角添加通知差不多
  • 编辑动画蓝图即可

设置击中敌人时的特效、音效与伤害

  • 给MainPlayer.h与BaseEnemy.h中添加一个粒子系统与声音组件
cpp 复制代码
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Hit Effect")
	class UParticleSystem* HitPaticles;

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Hit Effect")
	class USoundCue* HitSound;
  • 在WeaponItem.cpp的攻击开始重叠事件中去添加声音特效与伤害,这个就和之前装备武器与爆炸物传递伤害逻辑差不多
  • 正好复习一下,获取插槽的时候必须加const,因为获取插槽的函数返回的是一个const值
cpp 复制代码
void AWeaponItem::OnAttackCollisionOverlapBegin(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
	if (OtherActor)
	{
		ABaseEnemy* BaseEnemy = Cast<ABaseEnemy>(OtherActor);
		if (BaseEnemy)
		{
			if (BaseEnemy->HitPaticles)
			{
				//获取WeaponSocket插槽
				const USkeletalMeshSocket* WeaponScoket = ((USkeletalMeshComponent*)DisplayMesh)->GetSocketByName("WeaponSocket");
				if (WeaponScoket)
				{
					FVector SocketLocation = WeaponScoket->GetSocketLocation((USkeletalMeshComponent*)DisplayMesh);
					UGameplayStatics::SpawnEmitterAtLocation(this, BaseEnemy->HitPaticles, SocketLocation, FRotator(0.f), true);
				}
				if (BaseEnemy->HitSound)
				{
					UGameplayStatics::PlaySound2D(this, BaseEnemy->HitSound);
				}
				if (DamageTyClass)
				{
					UGameplayStatics::ApplyDamage(BaseEnemy, Damage, WeaponOwner, this, DamageTyClass);
				}
			}
		}
	}
}
  • 将攻击类型添加到每把剑上,人物添加上粒子与音效


设置击中玩家时的特效、音效与伤害

  • 基本与上面一样,在BaseEnemy.cpp的左右攻击事件中添加粒子音效与伤害
cpp 复制代码
void ABaseEnemy::OnLeftAttackCollisionOverlapBegin(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
	if (OtherActor)
	{
		AMainPlayer* Player = Cast<AMainPlayer>(OtherActor);
		if (Player)
		{
			if (Player->HitPaticles)
			{
				const USkeletalMeshSocket* AttackScoket = GetMesh()->GetSocketByName("LeftAttackSocket");
				if (AttackScoket)
				{
					FVector SocketLocation = AttackScoket->GetSocketLocation(GetMesh());
					UGameplayStatics::SpawnEmitterAtLocation(this, Player->HitPaticles, SocketLocation, FRotator(0.f), true);
				}
				if (Player->HitSound)
				{
					UGameplayStatics::PlaySound2D(this, Player->HitSound);
				}
				if (DamageTyClass)
				{
					UGameplayStatics::ApplyDamage(Player, Damage, AIController, this, DamageTyClass);
				}
			}
		}
	}
}

void ABaseEnemy::OnLeftAttackCollisionOverlapEnd(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex)
{
}

void ABaseEnemy::OnRightAttackCollisionOverlapBegin(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
	if (OtherActor)
	{
		AMainPlayer* Player = Cast<AMainPlayer>(OtherActor);
		if (Player)
		{
			if (Player->HitPaticles)
			{
				const USkeletalMeshSocket* AttackScoket = GetMesh()->GetSocketByName("RightAttackSocket");
				if (AttackScoket)
				{
					FVector SocketLocation = AttackScoket->GetSocketLocation(GetMesh());
					UGameplayStatics::SpawnEmitterAtLocation(this, Player->HitPaticles, SocketLocation, FRotator(0.f), true);
				}
				if (Player->HitSound)
				{
					UGameplayStatics::PlaySound2D(this, Player->HitSound);
				}
				if (DamageTyClass)
				{
					UGameplayStatics::ApplyDamage(Player, Damage, AIController, this, DamageTyClass);
				}
			}
		}
	}
}
  • 将攻击类型添加到敌人上,添加上粒子与音效

敌人受伤死亡分析

  • 敌人与主角类中添加死亡函数
  • 在敌人类中重写TakeDamage方法接收伤害,注意的是因为MainPlayer的血量UI是写在UI绑定事件蓝图里面在,而敌人血量UI是在C++中编码跟随,所以最后返回写完逻辑返回血量前,要更新一下血条
cpp 复制代码
	//重写TakeDamage方法
	float TakeDamage(float Damage, struct FDamageEvent const& DamageEvent, AController* EventInstigator, AActor* DamageCauser) override;

	void Die();
  • 伤害逻辑
cpp 复制代码
float ABaseEnemy::TakeDamage(float Damage, FDamageEvent const& DamageEvent, AController* EventInstigator, AActor* DamageCauser)
{
	if (Health - Damage <= 0.f)
	{
		Health = FMath::Clamp(Health - Damage, 0.f, MaxHealth);
		Die();
	}
	else
	{
		Health -= Damage;
	}

	HealthBar->SetPercent(Health / MaxHealth);//更新UI血条
	return Health;
}

按布尔类型播放死亡动画

  • 思路:我们还是可以使用notify来通知调用死亡动画,首先在MainPlayer与BaseEnemy中新建一个用于信息通知的函数
cpp 复制代码
UFUNCTION(BlueprintCallable)
void DeathEnd();
  • 然后编辑MainPlayer死亡函数的逻辑,首先主角状态设置为死亡,判断是否持剑,持剑就关闭武器所有碰撞
cpp 复制代码
void AMainPlayer::Die()
{
	SetMovementStatus(EPlayerMovementStatus::EPMS_Dead);
	if (EquipWeapon)
	{
		EquipWeapon->DeactiveAttackCollision();
		EquipWeapon->DeactiveDisplayMeshCollision();
	}
}
  • 编辑BaseEnemy死亡函数逻辑,首先设置状态为死亡 ,关闭所有的碰撞,因为敌人已死亡更新主角攻击目标
cpp 复制代码
void ABaseEnemy::Die()
{
	EnemyMovementStatus = EEnemyMovementStatus::EEMS_Dead;
	DeactiveLeftAttackCollision();
	DeactiveRightAttackCollision();
	ChaseVolume->SetCollisionEnabled(ECollisionEnabled::NoCollision);
	AttackVolume->SetCollisionEnabled(ECollisionEnabled::NoCollision);
	GetCapsuleComponent()->SetCollisionEnabled(ECollisionEnabled::NoCollision);

	//敌人死亡,主角要更新攻击目标
	Cast<AMainPlayer>(UGameplayStatics::GetPlayerPawn(this, 0))->UpdataAttackTarget();
}
  • 编辑他们的动画蓝图,添加Blend Poses by bool播放死亡动画,Enemy状态为死亡时播放死亡动画,否则就正常播放其他动画,记得将死亡的循环播放动画关闭

限制玩家死亡后的一切活动状态

  • 思路:我们在MainPlayer与BaseEnemy中新建一个专门用来判断玩家是否存活状态的函数,如果玩家不存活就停止一切活动
cpp 复制代码
FORCEINLINE bool IsAlive() { return MovementStatus != EPlayerMovementStatus::EPMS_Dead; }
  • 敌人的状态中还得检测一下玩家是否死亡
cpp 复制代码
FORCEINLINE bool IsAlive() { return EnemyMovementStatus != EEnemyMovementStatus::EEMS_Dead; }
bool HasValidTarget();

//------------------------------------------------------------------------------------------------
bool ABaseEnemy::HasValidTarget()
{
	return Cast<AMainPlayer>(UGameplayStatics::GetPlayerPawn(this, 0))->MovementStatus != EPlayerMovementStatus::EPMS_Dead;
}

MainPlayer.cpp

  • 然后在MainPlayer中添加判断禁止死亡后的一切活动,也就是都加上IsAlive判断
cpp 复制代码
// Fill out your copyright notice in the Description page of Project Settings.


#include "MainPlayer.h"
#include "GameFramework/SpringArmComponent.h"
#include "Camera/CameraComponent.h"
#include "Components/CapsuleComponent.h"
#include "Components/InputComponent.h"
#include "GameFramework/PlayerController.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "GamePlay/WeaponItem.h"
#include "Animation/AnimInstance.h"
#include "Characters/Enemy/BaseEnemy.h"
#include "Kismet/KismetMathLibrary.h"
// Sets default values
AMainPlayer::AMainPlayer()
{
 	// Set this character to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
	PrimaryActorTick.bCanEverTick = true;

	SpringArm = CreateDefaultSubobject<USpringArmComponent>(TEXT("SpringArm"));
	SpringArm->SetupAttachment(GetRootComponent());
	//设置SPringArm无碰撞臂长
	SpringArm->TargetArmLength = 600.f;
	SpringArm->bUsePawnControlRotation = true;//硬编码SpringArm继承controlller旋转为真


	FollowCamera = CreateDefaultSubobject<UCameraComponent>(TEXT("FollowCamera"));
	FollowCamera->SetupAttachment(SpringArm, NAME_None);
	FollowCamera->bUsePawnControlRotation = false;//硬编码FollowCamera继承controlller旋转为假

	//设置胶囊体的默认宽高
	GetCapsuleComponent()->SetCapsuleSize(35.f, 100.f);

	//对Character的Pawn进行硬编码
	bUseControllerRotationPitch = false;
	bUseControllerRotationYaw = false;
	bUseControllerRotationRoll = false;

	//硬编码orient Rotation to Movement,给个默认转向速率
	GetCharacterMovement()->bOrientRotationToMovement = true;
	GetCharacterMovement()->RotationRate = FRotator(0.f, 500.f, 0.f);

	//设置跳跃初始值与在空中的坠落时横向运动控制量
	GetCharacterMovement()->JumpZVelocity = 400.f;
	GetCharacterMovement()->AirControl = 0.15f;

	//给键盘控制转向的速率变量赋初值
	BaseTurnRate = 21.f;
	BaseLookUpRate = 21.f;

	//初始化角色状态
	MaxHealth = 100.f;
	Health = MaxHealth;
	MaxStamina = 200.f;
	Stamina = MaxStamina;
	StaminaConsumeRate = 20.f;
	ExhaustedStamina = 0.167f;
	Coins = 0;
	RunningSpeed = 600.f;
	SprintSpeed = 900.f;
	MovementStatus = EPlayerMovementStatus::EPMS_Normal;
	StaminaStatus = EPlayerStaminaStatus::EPSS_Normal;

	//默认没有按下shift
	bLeftShiftDown = false;


	InterpSpeed = 15.f;
	bInterpToEnemy = false;
}

// Called when the game starts or when spawned
void AMainPlayer::BeginPlay()
{
	Super::BeginPlay();
}

// Called every frame
void AMainPlayer::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);
	//不存活就不执行其他活动了
	if (!IsAlive())
	{
		return;
	}
	switch (StaminaStatus)
	{
	case EPlayerStaminaStatus::EPSS_Normal:
		//当Shift按下
		if (bLeftShiftDown)
		{
			if (Stamina - StaminaConsumeRate * DeltaTime <= MaxStamina * ExhaustedStamina)
			{
				StaminaStatus = EPlayerStaminaStatus::EPSS_Exhausted;
			}
			//无论是不是精疲力尽状态都要减去当前帧冲刺消耗的耐力
			Stamina -= StaminaConsumeRate * DeltaTime;
			SetMovementStatus(EPlayerMovementStatus::EPMS_Sprinting);
		}
		else
		{
			//当Shift没有按下,恢复耐力
			Stamina = FMath::Clamp(Stamina + StaminaConsumeRate * DeltaTime, 0.f, MaxStamina);
			SetMovementStatus(EPlayerMovementStatus::EPMS_Normal);
		}
		break;
	case EPlayerStaminaStatus::EPSS_Exhausted:
		if (bLeftShiftDown)
		{
			//如果耐力已经为0
			if (Stamina - StaminaConsumeRate * DeltaTime <= 0.f)
			{
				//么我们需要内部编码把shift抬起,此时StaminaStatus状态转换为ExhaustedRecovering状态,然后设置移动状态为Normal
				LeftShiftUp();
				StaminaStatus = EPlayerStaminaStatus::EPSS_ExhaustedRecovering;	
				SetMovementStatus(EPlayerMovementStatus::EPMS_Normal);
			}
			else
			{
				Stamina -= StaminaConsumeRate * DeltaTime;
			}
		}
		else
		{
			StaminaStatus = EPlayerStaminaStatus::EPSS_ExhaustedRecovering;
			Stamina = FMath::Clamp(Stamina + StaminaConsumeRate * DeltaTime, 0.f, MaxStamina);
			SetMovementStatus(EPlayerMovementStatus::EPMS_Normal);
		}
		break;
	case EPlayerStaminaStatus::EPSS_ExhaustedRecovering:
		//当恢复大于疲劳区时,StaminaStatus状态为Normal
		if (Stamina + StaminaConsumeRate * DeltaTime >= MaxStamina * ExhaustedStamina)
		{
			StaminaStatus = EPlayerStaminaStatus::EPSS_Normal;
		}
		//这状态值肯定是加定了
		Stamina += StaminaConsumeRate * DeltaTime;

		//抬起shift
		LeftShiftUp();
		SetMovementStatus(EPlayerMovementStatus::EPMS_Normal);
		break;
	default:
		break;
	}

	//进行转向插值
	if (bInterpToEnemy && AttackTarget)
	{
		//只需要AttackTarget的Yaw转向
		FRotator LookAtYaw(0.f, UKismetMathLibrary::FindLookAtRotation(GetActorLocation(), AttackTarget->GetActorLocation()).Yaw, 0.f);
		FRotator InterpRotation = FMath::RInterpTo(GetActorRotation(), LookAtYaw, DeltaTime, InterpSpeed);
		SetActorRotation(InterpRotation);
	}
}

// Called to bind functionality to input
void AMainPlayer::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
	Super::SetupPlayerInputComponent(PlayerInputComponent);
	//检查PlayerInputComponent指针,check函数只能在这使用
	check(PlayerInputComponent);

	//绑定跳跃轴映射事件
	PlayerInputComponent->BindAction("Jump", IE_Pressed, this, &AMainPlayer::Jump);//按下空格
	PlayerInputComponent->BindAction("Jump", IE_Released, this, &ACharacter::StopJumping);//抬起空格

	PlayerInputComponent->BindAction("Sprint", IE_Pressed, this, &AMainPlayer::LeftShiftDown);//按下shift
	PlayerInputComponent->BindAction("Sprint", IE_Released, this, &AMainPlayer::LeftShiftUp);//抬起shift

	//拾取剑
	PlayerInputComponent->BindAction("Interact", IE_Pressed, this, &AMainPlayer::InteractKeyDown);//按下F

	//攻击
	PlayerInputComponent->BindAction("Attack", IE_Pressed, this, &AMainPlayer::AttackKeyDown);
	PlayerInputComponent->BindAction("Attack", IE_Released, this, &AMainPlayer::AttackKeyUp);

	//绑定移动轴映射事件
	PlayerInputComponent->BindAxis("MoveForward", this, &AMainPlayer::MoveForward);
	PlayerInputComponent->BindAxis("MoveRight", this, &AMainPlayer::MoveRight);

	//绑定Controller控制器去管理视角旋转
	PlayerInputComponent->BindAxis("Turn", this, &AMainPlayer::Turn);
	PlayerInputComponent->BindAxis("LookUp", this, &AMainPlayer::LookUp);

	//绑定键盘鼠标轴映射事件
	PlayerInputComponent->BindAxis("TurnRate", this, &AMainPlayer::TurnRate);
	PlayerInputComponent->BindAxis("LookUpRate", this, &AMainPlayer::LookUpRate);

}

void AMainPlayer::Jump()
{
	//继承父类的方法
	if (IsAlive())
	{
		Super::Jump();
	}
	
}

void AMainPlayer::MoveForward(float value)
{
	if (Controller != nullptr && value != 0.f && !(bIsAttacking) && IsAlive())
	{
		//获取到Control旋转
		FRotator Rotation = Controller->GetControlRotation();
		//转向只关注水平Yaw方向,因此置0防止影响
		FRotator YowRotation = FRotator(0.0f, Rotation.Yaw, 0.0f);
		//获取相机(鼠标控制器的朝向),并且朝这个轴的方向移动
		FVector Direction = FRotationMatrix(YowRotation).GetUnitAxis(EAxis::X);
		AddMovementInput(Direction, value);
	}

}

void AMainPlayer::MoveRight(float value)
{
	if (Controller != nullptr && value != 0.f && !(bIsAttacking) && IsAlive())
	{
		//获取到Controller旋转
		FRotator Rotation = Controller->GetControlRotation();
		//转向只关注水平Yaw方向,因此置0防止影响
		FRotator YowRotation = FRotator(0.0f, Rotation.Yaw, 0.0f);
		//获取相机(鼠标控制器的朝向),并且朝这个轴的方向移动
		FVector Direction = FRotationMatrix(YowRotation).GetUnitAxis(EAxis::Y);
		AddMovementInput(Direction, value);
	}
}

void AMainPlayer::Turn(float Value)
{
	if (Value != 0.f && IsAlive())
	{
		AddControllerYawInput(Value);
	}
	
}

void AMainPlayer::LookUp(float Value)
{
	//UE_LOG(LogTemp, Warning, TEXT("%f"), GetControlRotation().Pitch);
	if (IsAlive())
	{
		//控制视角
		if (GetControlRotation().Pitch < 270.f && GetControlRotation().Pitch >180.f && Value > 0.f)
		{
			return;
		}
		else if (GetControlRotation().Pitch < 180.f && GetControlRotation().Pitch >45.f && Value < 0.f)
		{
			return;
		}
		AddControllerPitchInput(Value);
	}
}

void AMainPlayer::TurnRate(float Rate)
{
	//要乘以一个DeltaTime这样就可以避免高帧底帧差值问题
	float Value = Rate * BaseTurnRate * GetWorld()->GetDeltaSeconds();
	if (Value != 0.f && IsAlive())
	{
		AddControllerYawInput(Value);
	}
}

void AMainPlayer::LookUpRate(float Rate)
{
	//要乘以一个DeltaTime这样就可以避免高帧底帧差值问题
	if (IsAlive())
	{
		float Value = Rate * BaseLookUpRate * GetWorld()->GetDeltaSeconds();
		//控制视角
		if (GetControlRotation().Pitch < 270.f && GetControlRotation().Pitch >180.f && Value > 0.f)
		{
			return;
		}
		else if (GetControlRotation().Pitch < 180.f && GetControlRotation().Pitch >45.f && Value < 0.f)
		{
			return;
		}
		AddControllerPitchInput(Value);
	}

}

void AMainPlayer::AddHealth(float value)
{
	Health = FMath::Clamp(Health + value, 0.f, MaxHealth);
}

void AMainPlayer::AddStamina(float value)
{
	Stamina = FMath::Clamp(Stamina + value, 0.f, MaxStamina);
}

void AMainPlayer::AddCoin(float value)
{
	Coins += value;
}

float AMainPlayer::TakeDamage(float Damage, FDamageEvent const& DamageEvent, AController* EventInstigator, AActor* DamageCauser)
{
	if (Health - Damage <= 0.f)
	{
		Health = FMath::Clamp(Health - Damage, 0.f, MaxHealth);
		//TODO Die();
	}
	else
	{
		Health -= Damage;
	}
	return Health;
}

void AMainPlayer::SetMovementStatus(EPlayerMovementStatus Status)
{
	if (IsAlive())
	{
		MovementStatus = Status;
		//切换状态的时候改变移动速度
		switch (MovementStatus)
		{
		case EPlayerMovementStatus::EPMS_Sprinting:
			GetCharacterMovement()->MaxWalkSpeed = SprintSpeed;
			break;
		default:
			GetCharacterMovement()->MaxWalkSpeed = RunningSpeed;
			break;
		}
	}
}

void AMainPlayer::InteractKeyDown()
{
	if (OverlapWeapon && IsAlive())
	{
		if (EquipWeapon)
		{
			//交换武器
			EquipWeapon->UnEuip(this);
			OverlapWeapon->Equip(this);
		}
		else
		{
			//装备武器
			OverlapWeapon->Equip(this);
		}
	}
	else
	{
		if (EquipWeapon)
		{
			//卸载武器
			EquipWeapon->UnEuip(this);
		}
	}
}

void AMainPlayer::AttackKeyDown()
{
	if (IsAlive())
	{
		bAttackKeyDown = true;
		if (bIsWeapon)
		{
			AttackBegin();
		}
	}

}


void AMainPlayer::AttackBegin()
{
	if (!bIsAttacking && IsAlive())
	{
		bIsAttacking = true;
		bInterpToEnemy = true;
		//拿到动画
		UAnimInstance* AnimInstance = GetMesh()->GetAnimInstance();

		if (AnimInstance && AttackMontage)
		{
			float PlayRate = FMath::RandRange(1.25f, 1.75f);
			FString SectionName = FString::FromInt(FMath::RandRange(1, 2));
			//指定片段播放
			AnimInstance->Montage_Play(AttackMontage, PlayRate);
			AnimInstance->Montage_JumpToSection(FName(*SectionName), AttackMontage);
		}
	}
}

void AMainPlayer::AttackEnd()
{
	bIsAttacking = false;
	bInterpToEnemy = false;
	//形成闭环
	if (bAttackKeyDown && IsAlive())
	{
		AttackKeyDown();
	}
}

void AMainPlayer::UpdataAttackTarget()
{

	TArray<AActor*> OVerlappingActors;
	GetOverlappingActors(OVerlappingActors,EnemyFilter);

	//判断列表里面是否为空,为空就无攻击目标
	if (OVerlappingActors.Num() == 0)
	{
		AttackTarget = nullptr;
		return;
	}

	ABaseEnemy* ClosestDistance = nullptr;
	float MinDistance = 1000.f;
	FVector Loation = GetActorLocation();

	for (auto Actor : OVerlappingActors)
	{
		ABaseEnemy* Enemy = Cast<ABaseEnemy>(Actor);
		if (Enemy && Enemy->EnemyMovementStatus != EEnemyMovementStatus::EEMS_Dead)
		{
			float DistanceToActor = (Enemy->GetActorLocation() - Loation).Size();//记录当前位置Enemy距离MainPlayer位置
			if (DistanceToActor < MinDistance)
			{
				MinDistance = DistanceToActor;
				ClosestDistance = Enemy;
			}
		}
	}
	AttackTarget = ClosestDistance;
}

void AMainPlayer::Die()
{
	SetMovementStatus(EPlayerMovementStatus::EPMS_Dead);
	if (EquipWeapon)
	{
		EquipWeapon->DeactiveAttackCollision();
		EquipWeapon->DeactiveDisplayMeshCollision();
	}
}

void AMainPlayer::DeathEnd()
{
}

限制敌人死亡后的一切活动状态

  • 基本跟限制玩家差不多,只不过有些位置得多一个玩家是否存活的判断

BaseEnemy.cpp

cpp 复制代码
// Fill out your copyright notice in the Description page of Project Settings.


#include "BaseEnemy.h"
#include "Components/SphereComponent.h"
#include "Components/SkeletalMeshComponent.h"
#include "Components/CapsuleComponent.h"
#include "AIController.h"
#include "Characters/Player/MainPlayer.h"
#include "Animation/AnimInstance.h"
#include "Kismet/KismetMathLibrary.h"
#include "Kismet/GameplayStatics.h"
#include "Components/WidgetComponent.h"
#include "Blueprint/UserWidget.h"
#include "Components/ProgressBar.h"
#include "Components/BoxComponent.h"
#include "Sound/SoundCue.h"
#include "Engine/SkeletalMeshSocket.h"
#include "Characters/Player/MainPlayer.h"
// Sets default values
ABaseEnemy::ABaseEnemy()
{
 	// Set this character to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
	PrimaryActorTick.bCanEverTick = true;
	
	ChaseVolume = CreateDefaultSubobject<USphereComponent>(TEXT("ChaseVolume"));
	ChaseVolume->SetupAttachment(GetRootComponent());
	ChaseVolume->InitSphereRadius(800.f);
	ChaseVolume->SetCollisionObjectType(ECollisionChannel::ECC_WorldDynamic);
	ChaseVolume->SetCollisionResponseToAllChannels(ECollisionResponse::ECR_Ignore);
	ChaseVolume->SetCollisionResponseToChannel(ECollisionChannel::ECC_Pawn, ECollisionResponse::ECR_Overlap);

	AttackVolume = CreateDefaultSubobject<USphereComponent>(TEXT("AttackVolume"));
	AttackVolume->SetupAttachment(GetRootComponent());
	AttackVolume->InitSphereRadius(100.f);
	AttackVolume->SetCollisionObjectType(ECollisionChannel::ECC_WorldDynamic);
	AttackVolume->SetCollisionResponseToAllChannels(ECollisionResponse::ECR_Ignore);
	AttackVolume->SetCollisionResponseToChannel(ECollisionChannel::ECC_Pawn, ECollisionResponse::ECR_Overlap);
	
	HealthBarWidgetComponent = CreateDefaultSubobject<UWidgetComponent>(TEXT("HealthBarWidgetComponent"));
	HealthBarWidgetComponent->SetupAttachment(GetRootComponent());
	HealthBarWidgetComponent->SetWidgetSpace(EWidgetSpace::Screen);
	HealthBarWidgetComponent->SetDrawSize(FVector2D(125.f, 10.f));
	HealthBarWidgetComponent->SetWorldLocation(FVector(0.f, 0.f, 50.f));

	LeftAttackCollision = CreateDefaultSubobject<UBoxComponent>(TEXT("LeftAttackCollision"));
	LeftAttackCollision->SetupAttachment(GetMesh(), "LeftAttackSocket");
	DeactiveLeftAttackCollision();

	RightAttackCollision = CreateDefaultSubobject<UBoxComponent>(TEXT("RightAttackCollision"));
	RightAttackCollision->SetupAttachment(GetMesh(), "RightAttackSocket");
	DeactiveRightAttackCollision();

	//避免摄像机被敌人给阻挡
	GetMesh()->SetCollisionResponseToChannel(ECollisionChannel::ECC_Camera, ECollisionResponse::ECR_Ignore);
	GetCapsuleComponent()->SetCollisionResponseToChannel(ECollisionChannel::ECC_Camera, ECollisionResponse::ECR_Ignore);
	//设置持有属性
	AutoPossessAI = EAutoPossessAI::PlacedInWorldOrSpawned;

	//初始化默认移动状态
	EnemyMovementStatus = EEnemyMovementStatus::EEMS_Idle;

	InterpSpeed = 15.f;
	bInterpToPlayer = false;

	MaxHealth = 100.f;
	Health = MaxHealth;

	Damage = 5.f;
}

// Called when the game starts or when spawned
void ABaseEnemy::BeginPlay()
{
	Super::BeginPlay();

	ChaseVolume->OnComponentBeginOverlap.AddDynamic(this, &ABaseEnemy::OnChaseVolumeOverlapBegin);
	ChaseVolume->OnComponentEndOverlap.AddDynamic(this, &ABaseEnemy::OnChaseVolumeOverlapEnd);

	AttackVolume->OnComponentBeginOverlap.AddDynamic(this, &ABaseEnemy::OnAttackVolumeOverlapBegin);
	AttackVolume->OnComponentEndOverlap.AddDynamic(this, &ABaseEnemy::OnAttackVolumeOverlapEnd);

	LeftAttackCollision->OnComponentBeginOverlap.AddDynamic(this, &ABaseEnemy::OnLeftAttackCollisionOverlapBegin);
	LeftAttackCollision->OnComponentEndOverlap.AddDynamic(this, &ABaseEnemy::OnLeftAttackCollisionOverlapEnd);

	RightAttackCollision->OnComponentBeginOverlap.AddDynamic(this, &ABaseEnemy::OnRightAttackCollisionOverlapBegin);
	RightAttackCollision->OnComponentEndOverlap.AddDynamic(this, &ABaseEnemy::OnRightAttackCollisionOverlapEnd);
	
	//获取到HealBar小组件
	HealthBar = Cast<UProgressBar>(HealthBarWidgetComponent->GetUserWidgetObject()->GetWidgetFromName("HealthBar"));
	HealthBar->SetPercent(Health / MaxHealth);
	HealthBar->SetVisibility(ESlateVisibility::Hidden);//一开始不显示血条

	//拿到Controller
	AIController = Cast<AAIController>(GetController());
}

// Called every frame
void ABaseEnemy::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);

	if (bInterpToPlayer && HasValidTarget() && IsAlive())
	{
		FRotator LookYaw(0.f, UKismetMathLibrary::FindLookAtRotation(GetActorLocation(), UGameplayStatics::GetPlayerPawn(this, 0)->GetActorLocation()).Yaw, 0.f);
		FRotator InterpRotation = FMath::RInterpTo(GetActorRotation(), LookYaw, DeltaTime, InterpSpeed);
		SetActorRotation(InterpRotation);
	}
}

// Called to bind functionality to input
void ABaseEnemy::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
	Super::SetupPlayerInputComponent(PlayerInputComponent);

}

void ABaseEnemy::OnChaseVolumeOverlapBegin(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
	if (OtherActor && IsAlive())
	{
		AMainPlayer* Player = Cast<AMainPlayer>(OtherActor);
		if (Player)
		{
			//主角进入追逐范围显示血条
			HealthBar->SetVisibility(ESlateVisibility::Visible);
			MoveToTarget(Player);
		}
	}
}

void ABaseEnemy::OnChaseVolumeOverlapEnd(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex)
{
	if (OtherActor && IsAlive())
	{
		AMainPlayer* Player = Cast<AMainPlayer>(OtherActor);
		if (Player)
		{
			if (AIController)
			{
				//主角出追逐范围不显示血条
				HealthBar->SetVisibility(ESlateVisibility::Hidden);
				//停止移动
				AIController->StopMovement();
			}
		}
	}
}

void ABaseEnemy::OnAttackVolumeOverlapBegin(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
	if (OtherActor && IsAlive())
	{
		AMainPlayer* Player = Cast<AMainPlayer>(OtherActor);
		if (Player)
		{
			Player->UpdataAttackTarget();
			bAttackVolumeOverlap = true;
			AttackBegin();
		}
	}
}

void ABaseEnemy::OnAttackVolumeOverlapEnd(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex)
{
	if (OtherActor && IsAlive())
	{
		AMainPlayer* Player = Cast<AMainPlayer>(OtherActor);
		if (Player)
		{
			bAttackVolumeOverlap = false;
			if (EnemyMovementStatus!=EEnemyMovementStatus::EEMS_Attacking)
			{
				MoveToTarget(Player);
			}
		}
	}
}

void ABaseEnemy::MoveToTarget(AMainPlayer* Player)
{
	if (IsAlive())
	{
		EnemyMovementStatus = EEnemyMovementStatus::EEMS_MoveToTarget;
		if (AIController)
		{
			FAIMoveRequest MoveRequest;
			MoveRequest.SetGoalActor(Player);//设置移动请求目标
			MoveRequest.SetAcceptanceRadius(10.f);	//设置移动半径

			FNavPathSharedPtr NavPath;//会返回路径

			AIController->MoveTo(MoveRequest, &NavPath);
		}
	}
}

void ABaseEnemy::AttackBegin()
{
	if (HasValidTarget() && IsAlive())
	{
		//攻击中关闭移动
		if (AIController)
		{
			AIController->StopMovement();
		}
		if (EnemyMovementStatus != EEnemyMovementStatus::EEMS_Attacking)
		{
			EnemyMovementStatus = EEnemyMovementStatus::EEMS_Attacking;

			bInterpToPlayer = true;

			UAnimInstance* AnimInstance = GetMesh()->GetAnimInstance();
			if (AnimInstance && AttackMontage)
			{
				float PlayRate = FMath::RandRange(0.9f, 1.1f);
				FString SectionName = FString::FromInt(FMath::RandRange(1, 3));
				AnimInstance->Montage_Play(AttackMontage, PlayRate);
				AnimInstance->Montage_JumpToSection(FName(*SectionName), AttackMontage);
			}
		}
	}
}

void ABaseEnemy::AttackEnd()
{
	bInterpToPlayer = false;
	if (HasValidTarget() && IsAlive())
	{
		EnemyMovementStatus = EEnemyMovementStatus::EEMS_Idle;
		if (bAttackVolumeOverlap && HasValidTarget() && IsAlive())
		{
			AttackBegin();
		}
	}
}

void ABaseEnemy::OnLeftAttackCollisionOverlapBegin(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
	if (OtherActor && IsAlive())
	{
		AMainPlayer* Player = Cast<AMainPlayer>(OtherActor);
		if (Player)
		{
			if (Player->HitPaticles)
			{
				const USkeletalMeshSocket* AttackScoket = GetMesh()->GetSocketByName("LeftAttackSocket");
				if (AttackScoket)
				{
					FVector SocketLocation = AttackScoket->GetSocketLocation(GetMesh());
					UGameplayStatics::SpawnEmitterAtLocation(this, Player->HitPaticles, SocketLocation, FRotator(0.f), true);
				}
				if (Player->HitSound)
				{
					UGameplayStatics::PlaySound2D(this, Player->HitSound);
				}
				if (DamageTyClass)
				{
					UGameplayStatics::ApplyDamage(Player, Damage, AIController, this, DamageTyClass);
				}
			}
		}
	}
}

void ABaseEnemy::OnLeftAttackCollisionOverlapEnd(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex)
{
}

void ABaseEnemy::OnRightAttackCollisionOverlapBegin(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
	if (OtherActor && IsAlive())
	{
		AMainPlayer* Player = Cast<AMainPlayer>(OtherActor);
		if (Player)
		{
			if (Player->HitPaticles)
			{
				const USkeletalMeshSocket* AttackScoket = GetMesh()->GetSocketByName("RightAttackSocket");
				if (AttackScoket)
				{
					FVector SocketLocation = AttackScoket->GetSocketLocation(GetMesh());
					UGameplayStatics::SpawnEmitterAtLocation(this, Player->HitPaticles, SocketLocation, FRotator(0.f), true);
				}
				if (Player->HitSound)
				{
					UGameplayStatics::PlaySound2D(this, Player->HitSound);
				}
				if (DamageTyClass)
				{
					UGameplayStatics::ApplyDamage(Player, Damage/2, AIController, this, DamageTyClass);
				}
			}
		}
	}
}

void ABaseEnemy::OnRightAttackCollisionOverlapEnd(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex)
{
}

void ABaseEnemy::ActiveLeftAttackCollision()
{
	LeftAttackCollision->SetCollisionEnabled(ECollisionEnabled::QueryOnly);
	LeftAttackCollision->SetCollisionObjectType(ECollisionChannel::ECC_WorldDynamic);
	LeftAttackCollision->SetCollisionResponseToAllChannels(ECollisionResponse::ECR_Ignore);
	LeftAttackCollision->SetCollisionResponseToChannel(ECollisionChannel::ECC_Pawn, ECollisionResponse::ECR_Overlap);
}

void ABaseEnemy::DeactiveLeftAttackCollision()
{
	LeftAttackCollision->SetCollisionEnabled(ECollisionEnabled::NoCollision);
}

void ABaseEnemy::ActiveRightAttackCollision()
{
	RightAttackCollision->SetCollisionEnabled(ECollisionEnabled::QueryOnly);
	RightAttackCollision->SetCollisionObjectType(ECollisionChannel::ECC_WorldDynamic);
	RightAttackCollision->SetCollisionResponseToAllChannels(ECollisionResponse::ECR_Ignore);
	RightAttackCollision->SetCollisionResponseToChannel(ECollisionChannel::ECC_Pawn, ECollisionResponse::ECR_Overlap);
}

void ABaseEnemy::DeactiveRightAttackCollision()
{
	RightAttackCollision->SetCollisionEnabled(ECollisionEnabled::NoCollision);
}

float ABaseEnemy::TakeDamage(float Damage, FDamageEvent const& DamageEvent, AController* EventInstigator, AActor* DamageCauser)
{
	if (Health - Damage <= 0.f)
	{
		Health = FMath::Clamp(Health - Damage, 0.f, MaxHealth);
		Die();
	}
	else
	{
		Health -= Damage;
	}

	HealthBar->SetPercent(Health / MaxHealth);//更新UI血条
	return Health;
}

void ABaseEnemy::Die()
{
	EnemyMovementStatus = EEnemyMovementStatus::EEMS_Dead;
	DeactiveLeftAttackCollision();
	DeactiveRightAttackCollision();
	ChaseVolume->SetCollisionEnabled(ECollisionEnabled::NoCollision);
	AttackVolume->SetCollisionEnabled(ECollisionEnabled::NoCollision);
	GetCapsuleComponent()->SetCollisionEnabled(ECollisionEnabled::NoCollision);

	//敌人死亡,主角要更新攻击目标
	Cast<AMainPlayer>(UGameplayStatics::GetPlayerPawn(this, 0))->UpdataAttackTarget();
}

void ABaseEnemy::DeathEnd()
{
}

bool ABaseEnemy::HasValidTarget()
{
	return Cast<AMainPlayer>(UGameplayStatics::GetPlayerPawn(this, 0))->MovementStatus != EPlayerMovementStatus::EPMS_Dead;
}

主角死亡与敌人死亡后延时销毁

  • 首先设置敌人血条在敌人死亡后就隐藏
cpp 复制代码
void ABaseEnemy::Die()
{
	EnemyMovementStatus = EEnemyMovementStatus::EEMS_Dead;
	HealthBar->SetVisibility(ESlateVisibility::Hidden);

	DeactiveLeftAttackCollision();
	DeactiveRightAttackCollision();
	ChaseVolume->SetCollisionEnabled(ECollisionEnabled::NoCollision);
	AttackVolume->SetCollisionEnabled(ECollisionEnabled::NoCollision);
	GetCapsuleComponent()->SetCollisionEnabled(ECollisionEnabled::NoCollision);

	//敌人死亡,主角要更新攻击目标
	Cast<AMainPlayer>(UGameplayStatics::GetPlayerPawn(this, 0))->UpdataAttackTarget();
}
  • 然后去主角的死亡动画中添加DeathEnd的通知,并在动画蓝图中调用

  • 编写DeathEnd死亡逻辑,首先关闭动画,添加定时器,玩家死后一秒重启游戏
cpp 复制代码
void AMainPlayer::DeathEnd()
{
	GetMesh()->bPauseAnims = true;
	GetMesh()->bNoSkeletonUpdate = true;

	FTimerHandle DeathTimerHandle;
	auto Lambda = []()
	{
		//TODO RestartLevel();
	};
	GetWorldTimerManager().SetTimer(DeathTimerHandle, FTimerDelegate::CreateLambda(Lambda), 1.0, false);
}
  • 敌人这边也是一样添加通知动画蓝图中调用,然后编写死亡消失逻辑

  • 死亡一秒后销毁
cpp 复制代码
void ABaseEnemy::DeathEnd()
{
	GetMesh()->bPauseAnims = true;
	GetMesh()->bNoSkeletonUpdate = true;

	FTimerHandle DeathTimerHandle;
	auto Lambda = [this]()
	{
		Destroy();
	};
	GetWorldTimerManager().SetTimer(DeathTimerHandle, FTimerDelegate::CreateLambda(Lambda), 1.0, false);
}

玩家死亡重新开始游戏

  • 将上面遗留的RestartLevel函数进行新建然后编写,用UGameplayStatics中的方法进行重新开始游戏
cpp 复制代码
void AMainPlayer::DeathEnd()
{

	GetMesh()->bPauseAnims = true;
	GetMesh()->bNoSkeletonUpdate = true;

	FTimerHandle DeathTimerHandle;
	auto Lambda = [this]()
	{
		RestartLevel();
	};
	GetWorldTimerManager().SetTimer(DeathTimerHandle, FTimerDelegate::CreateLambda(Lambda), 1.0, false);

}

void AMainPlayer::RestartLevel()
{
	FString LevelName = UGameplayStatics::GetCurrentLevelName(this);
	UGameplayStatics::OpenLevel(this, FName(*LevelName));
}
相关推荐
何曾参静谧4 分钟前
「QT」文件类 之 QTextStream 文本流类
开发语言·qt
monkey_meng7 分钟前
【Rust类型驱动开发 Type Driven Development】
开发语言·后端·rust
虾球xz15 分钟前
游戏引擎学习第11天
stm32·学习·游戏引擎
落落落sss16 分钟前
MQ集群
java·服务器·开发语言·后端·elasticsearch·adb·ruby
心怀梦想的咸鱼17 分钟前
Ue5 umg学习(三)文本控件
学习·ue5
心怀梦想的咸鱼19 分钟前
Ue5 umg学习(二)图像控件,锚点
学习·ue5
可均可可32 分钟前
C++之OpenCV入门到提高005:005 图像操作
c++·图像处理·opencv·图像操作
2401_8532757336 分钟前
ArrayList 源码分析
java·开发语言
zyx没烦恼36 分钟前
【STL】set,multiset,map,multimap的介绍以及使用
开发语言·c++
lb363636363636 分钟前
整数储存形式(c基础)
c语言·开发语言