UEC++ day8

news2024/11/17 5:52:20

伤害系统

给敌人创建血条

  • 首先添加一个UI界面用来显示敌人血条
  • 设置背景图像为黑色半透明
    在这里插入图片描述
  • 填充颜色
    在这里插入图片描述
  • 给敌人类添加两种状态表示血量与最大血量,添加一个UWidegtComponet组件与UProgressBar组件
	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组件
	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小组件
// 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());
}
  • 运行结果
    在这里插入图片描述
    在这里插入图片描述

血条进入敌人追逐区显示

  • 一开始敌人血条是不显示的,只有主角进入了敌人追逐区才开始显示血条,出了追逐区也不显示
	//获取到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中内置的直接伤害附加的功能,类似爆炸物那一节,所以我们要在武器类中新建一个盒子碰撞触发器,新建一个伤害值,新建一个伤害类型,新建一个伤害发起者
	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中去切换的所以需要添加反射
	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”
	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
// 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);
}

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

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

给敌人添加触发器

  • 基本与上面给主角添加伤害需求的思路一致
  • 先给敌人添加两个骨骼
    在这里插入图片描述

触发伤害需求

  • 再来就是编辑敌人类的变量需求与基本逻辑了,两个触发盒子因为是两只腿的攻击,新建伤害值,伤害类型
	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;
  • 不用说,现在是四个伤害触发事件的新建,四个动态切换碰撞的函数
	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();
  • 现在就可以开始编写,初始化了
	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
// 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中添加一个粒子系统与声音组件
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Hit Effect")
	class UParticleSystem* HitPaticles;

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Hit Effect")
	class USoundCue* HitSound;
  • 在WeaponItem.cpp的攻击开始重叠事件中去添加声音特效与伤害,这个就和之前装备武器与爆炸物传递伤害逻辑差不多
  • 正好复习一下,获取插槽的时候必须加const,因为获取插槽的函数返回的是一个const值
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的左右攻击事件中添加粒子音效与伤害
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++中编码跟随,所以最后返回写完逻辑返回血量前,要更新一下血条
	//重写TakeDamage方法
	float TakeDamage(float Damage, struct FDamageEvent const& DamageEvent, AController* EventInstigator, AActor* DamageCauser) override;

	void Die();
  • 伤害逻辑
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中新建一个用于信息通知的函数
UFUNCTION(BlueprintCallable)
void DeathEnd();
  • 然后编辑MainPlayer死亡函数的逻辑,首先主角状态设置为死亡,判断是否持剑,持剑就关闭武器所有碰撞
void AMainPlayer::Die()
{
	SetMovementStatus(EPlayerMovementStatus::EPMS_Dead);
	if (EquipWeapon)
	{
		EquipWeapon->DeactiveAttackCollision();
		EquipWeapon->DeactiveDisplayMeshCollision();
	}
}
  • 编辑BaseEnemy死亡函数逻辑,首先设置状态为死亡 ,关闭所有的碰撞,因为敌人已死亡更新主角攻击目标
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中新建一个专门用来判断玩家是否存活状态的函数,如果玩家不存活就停止一切活动
FORCEINLINE bool IsAlive() { return MovementStatus != EPlayerMovementStatus::EPMS_Dead; }

  • 敌人的状态中还得检测一下玩家是否死亡
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判断
// 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

// 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;
}

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

  • 首先设置敌人血条在敌人死亡后就隐藏
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死亡逻辑,首先关闭动画,添加定时器,玩家死后一秒重启游戏
void AMainPlayer::DeathEnd()
{
	GetMesh()->bPauseAnims = true;
	GetMesh()->bNoSkeletonUpdate = true;

	FTimerHandle DeathTimerHandle;
	auto Lambda = []()
	{
		//TODO RestartLevel();
	};
	GetWorldTimerManager().SetTimer(DeathTimerHandle, FTimerDelegate::CreateLambda(Lambda), 1.0, false);
}
  • 敌人这边也是一样添加通知动画蓝图中调用,然后编写死亡消失逻辑
    在这里插入图片描述
    在这里插入图片描述
  • 死亡一秒后销毁
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中的方法进行重新开始游戏
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));
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1242552.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

怎样通过代理ip提高上网速度

在当今互联网高度发达的时代&#xff0c;我们经常需要使用代理IP来隐藏自己的真实IP地址或提高网络连接速度。然而&#xff0c;有些用户可能会遇到代理IP无法提高网络速度的情况。那么&#xff0c;如何通过代理IP提高上网速度呢&#xff1f;以下是几个技巧&#xff1a; 1.选择…

oracle数据库巡检常见脚本-系列三

简介 作为数据库管理员&#xff08;DBA&#xff09;&#xff0c;定期进行数据库的日常巡检是非常重要的。以下是一些原因&#xff1a; 保证系统的稳定性&#xff1a;通过定期巡检&#xff0c;DBA可以发现并及时解决可能导致系统不稳定的问题&#xff0c;如性能瓶颈、资源利用率…

G320E是一种低荣声、固定频率的电荷泵型DC/DC转换器

G320E 低噪声电荷泵DC/DC转换器 产品概述&#xff1a; G320E是一种低荣声、固定频率的电荷泵型DC/DC转换器&#xff0c;在输入电压范围在2.7V到5.0V的情况下&#xff0c;该器件可以产生5V的输出电压&#xff0c;最大输出电流达到300mA.G320E外部元件少&#xff0c;非常适合于…

LangChain 7 文本模型TextLangChain和聊天模型ChatLangChain

LangChain系列文章 LangChain 实现给动物取名字&#xff0c;LangChain 2模块化prompt template并用streamlit生成网站 实现给动物取名字LangChain 3使用Agent访问Wikipedia和llm-math计算狗的平均年龄LangChain 4用向量数据库Faiss存储&#xff0c;读取YouTube的视频文本搜索I…

虚拟机解决Linux中Uos和Deepin登录密码忘记的问题 标题Linux Uos Deepin

Uos是切换网络模式解决的(之前有绑定过用户) 因为之前用的是桥接模式登录的时候一直无法联网,改为Nat模式后可以和电脑共用一个网络ip,可以重置密码了,以此解决 ps: 特别说明rw single init/bin/bash 方法和systemd.debug-shell1方法已经失效,不要再做无谓的尝试了Deepin23社区…

人工智能:一种现代的方法 第十四章 概率推理

文章目录 人工智能&#xff1a;一种现代的方法 第十四章 概率推理本章前言14.1 不确定性问题域中的知识表示14.1.1 联合概率分布14.1.2贝叶斯网络 14.2 贝叶斯网络的语义14.2.1表示联合概率分布14.2.2 紧致性14.2.3 节点排序14.2.4 贝叶斯网络中的条件独立关系14.3 条件分布的有…

痤疮分级实验笔记-ResNet

组织数据集 方式1&#xff1a;根据txt文件分类的数据集组织形式&#xff08;放弃&#xff09; 你可以使用Python来读取txt文件中的训练集图片信息&#xff0c;并将这些图片从原始文件夹复制到目标文件夹中。 当程序无法找到标签对应的图片或者目标文件夹中已经存在同名图片时…

IT支持团队的绩效指标和最佳实践

一名员工在远程时因笔记本问题寻求IT支持&#xff0c;尽管他们多次尝试排除故障&#xff0c;但由于缺乏专业知识&#xff0c;最终还是无法访问工作所需的应用程序。这时&#xff0c;他们需要一名专业的 IT 技术人员来指导他们&#xff0c;但他们只能等待有人注意到并回应他们的…

BMS基础知识:BMS基本功能,铅酸和锂电池工作原理,电池系统的重要概念!

笔者有话说&#xff1a; 作为BMS从业者来讲&#xff0c;目前接触的BMS系统并不是很高大尚&#xff0c;但基础功能都是有的。 关于BMS的基本功能&#xff0c;工作原理&#xff0c;运行逻辑等&#xff0c;在此做一个梳理&#xff0c;讲一些最基础的扫盲知识&#xff0c;可以作为…

安全牛《数据分类分级自动化建设指南》发布|美创入选代表厂商,分享智能化探索

近日&#xff0c;安全牛发布《数据分类分级自动化建设指南》研究报告&#xff0c;对数据分类分级的主要技术、实施要点、选型指导、发展趋势等展开深入探讨&#xff0c;为各行业数据分类分级自动化工作落地提供帮助与指引。 美创科技被列为代表推荐厂商&#xff0c;落地案例—农…

Flutter:引领移动开发新潮流,跨平台应用程序的终极解决方案

文章目录 一、介绍二、环境搭建三、基础组件四、生命周期管理五、路由控制六、网络请求七、数据存储八、调试与优化《从零基础到精通Flutter开发》特色内容简介作者简介目录获取方式 一、介绍 Flutter是由Google开发的一款开源移动应用开发框架&#xff0c;它可以帮助开发者快…

PS给图片增加一个白色边框。

问题描述&#xff1a;PS如何给图片增加一个白色边框&#xff1f; 解决办法&#xff1a; 第一步&#xff1a;使用shiftAltA快捷键&#xff0c;在图片四周拉出一个灰白色的边框。如下图所示&#xff1a; 第二步&#xff0c;使用快捷键Ctrlshiftn新建一个图层。 并把新建的图层…

C语言——利用函数递归,编写函数不允许创建临时变量,求字符串长度

#define _CRT_SECURE_NO_WARNINGS 1#include<stdio.h>int my_strlen(char* str) {if(*str ! \0)return 1my_strlen(str1);elsereturn 0; }int main() {char arr[] "hello";int len my_strlen(arr); //arr是数组&#xff0c;数组传参&#xff0c;传过去的是第…

推荐一款png图片打包plist工具pngPackerGUI_V2.0

png图片打包plist工具&#xff0c;手把手教你使用pngPackerGUI_V2.0 此软件是在pngpacker_V1.1软件基础之后&#xff0c;开发的界面化操作软件&#xff0c;方便不太懂命令行的小白快捷上手使用。1.下载并解压缩软件&#xff0c;得到如下目录&#xff0c;双击打开 pngPackerGUI.…

【广州华锐互动】VR线上课件制作软件满足数字化教学需求

随着科技的不断发展&#xff0c;虚拟现实&#xff08;VR&#xff09;技术在教学领域的应用逐渐成为趋势。其中&#xff0c;广州华锐互动开发的VR线上课件制作软件更是备受关注。这种工具为教师提供了便捷的制作VR课件的手段&#xff0c;使得VR教学成为可能&#xff0c;极大地丰…

2.4G射频收发芯片 XL2407P芯片介绍,集成九齐单片机

XL2407P芯片是工作在2.400~2.483GHz世界通用ISM频段,集成微控制器的的SOC无线收发芯片。该芯片集成射频收发机、频率收生器、晶体振荡器、调制解调器等功能模块,并且支持一-对多组网和带ACK的通信模式。发射输出功率、工作频道以及通信数据率均可配置。 芯片内含以EPROM作为内…

HTML5+ API 爬坑记录

背景: 有个比较早些使用5开发的项目, 最近两天反馈了一些问题, 解决过程在此记录; 坑1: plus.gallery.pick 选择图片没有进入回调 HTML5 API Reference 在 联想小新 平板电脑上选择相册图片进行上传时, 打开相册瞬间 应用会自动重启, 相册倒是有打开, 不过应用重启了, 导…

Ajax技

Ajax的特点 异步提交&#xff1a;Ajax采用异步通信方式&#xff0c;能够在页面无需重新加载的情况下向服务器发送请求并接收响应数据&#xff0c;提升了用户体验。无需插件&#xff1a;Ajax是基于标准浏览器的Javascript和XMLHttpRequest对象实现的&#xff0c;无需安装插件或…

HBase之Slow log

目录 Slow log记录条件日志示例配置内存存储配置表存储 Slow log hbase会记录慢查询日志&#xff0c;关键字包括responseTooSlow、responseTooLarge、operationTooSlow、and operationTooLarge。 记录条件 响应时间和响应的数据大小可由以下参数配置 hbase.ipc.warn.respon…

大型养殖场需要哪些污水处理设备

大型养殖场是一个涉及环境保护和可持续发展的关键行业&#xff0c;对于处理养殖场产生的污水有着明确的要求和标准。为了确保污水得到有效处理和处理效果达到国家排放标准&#xff0c;大型养殖场需要配备一系列污水处理设备。以下是几种常见的污水处理设备&#xff1a; 1. 水解…