UEC++ day8

伤害系统

给敌人创建血条

  • 首先添加一个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));
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

短视频变表情包gif怎么做?这一招最好用

Gif动态表情包是一种有效的表达感情的方式。可以通过添加图像、文字等更加直观的传递情感和信息。在各种聊天软件中gif动态表情包也是非常收欢迎的。当我们看到一段视频想要将其制作成gif动态表情包的时候要怎么操作呢&#xff1f;教大家使用在线制作gif&#xff08;https://ww…

低代码开发:云表颠覆传统,轻松破解应用开发周期长之困局

在传统的应用开发模式下&#xff0c;应用开发周期长一直是IT部门和业务部门头疼的问题。面对业务部门提出的一个又一个新的应用需求&#xff0c;IT部门往往应接不暇&#xff0c;难以一一满足。这种困境不仅使IT部门负担沉重&#xff0c;更导致业务部门因长时间的等待而心生不满…

SVD 最小二乘法解 亲测ok!

线性最小二乘问题 m个方程求解n个未知数&#xff0c;有三种情况&#xff1a; mn且A为非奇异&#xff0c;则有唯一解&#xff0c;xA.inverse()*bm>n&#xff0c;约束的个数大于未知数的个数&#xff0c;称为超定问题&#xff08;overdetermined&#xff09;m<n&#xff0…

多个视频怎么生成一个二维码?二维码看视频的制作方法

二维码能放入多个视频吗&#xff1f;现在用二维码看视频是很流行的一种方式&#xff0c;不仅符合现在人的行为习惯&#xff0c;而且还不需要占用自身的容量空间&#xff0c;能够即时的获取视频内容。那么当有多个视频需要展示&#xff0c;但是想要放到一个二维码中&#xff0c;…

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

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

Claude 发布2.1 版本,重大升级更新来看看有什么新功能?

11 月 23 日消息&#xff0c;OpenAI 竞争对手 Anthropic 日前推出了 Claude 2.1 聊天机器人及对应同名 AI 模型Claude 2.1&#xff0c;本文将总结Claude 2.1的主要功能。 增强的处理能力 上下文处理量大幅提升&#xff1a;Claude 2.1 现在能处理高达 200K上下文标记&#xff…

完美解决AttributeError: module ‘numpy‘ has no attribute ‘typeDict‘

文章目录 前言一、完美解决办法安装低版本1.21或者1.19.3都可以总结 前言 这个问题从表面看就是和numpy库相关&#xff0c;所以是小问题&#xff0c;经过来回调试安装numpy&#xff0c;发现是因为目前的版本太高&#xff0c;因此我们直接安装低版本numpy。也不用专门卸载目前的…

【Java从入门到大牛】网络编程

&#x1f525; 本文由 程序喵正在路上 原创&#xff0c;CSDN首发&#xff01; &#x1f496; 系列专栏&#xff1a;Java从入门到大牛 &#x1f320; 首发时间&#xff1a;2023年11月23日 &#x1f98b; 欢迎关注&#x1f5b1;点赞&#x1f44d;收藏&#x1f31f;留言&#x1f4…

Mybatis plus 简介

简介 MyBatis-Plus (opens new window)&#xff08;简称 MP&#xff09;是一个 MyBatis (opens new window)的增强工具&#xff0c;在 MyBatis 的基础上只做增强不做改变&#xff0c;为简化开发、提高效率而生。 官网:https://baomidou.com/pages/24112f/ 特性 无侵入&…

Mybatis一级缓存和二级缓存原理剖析与源码详解

Mybatis一级缓存和二级缓存原理剖析与源码详解 在本篇文章中&#xff0c;将结合示例与源码&#xff0c;对MyBatis中的一级缓存和二级缓存进行说明。 MyBatis版本&#xff1a;3.5.2 文章目录 Mybatis一级缓存和二级缓存原理剖析与源码详解⼀级缓存场景一场景二⼀级缓存原理探究…

安卓手机好用的清单软件有哪些?

生活中每个人都有丢三落四的习惯&#xff0c;伴随着生活节奏的加快&#xff0c;人们常忘事的情况会更加频繁的出现&#xff0c;这时候很多人就开始选择手机上记录清单类的软件&#xff0c;安卓手机在手机市场中占有很大的分量&#xff0c;在安卓手机上好用的记录清单的软件有哪…

别熬夜了!人真的会变臭

身为当代社畜&#xff0c;你一定经历过如下瞬间——— 周一早高峰的地铁车厢&#xff0c;拥挤的人群里若有若无地飘荡出一股刺鼻臭味&#xff0c;即使戴着口罩也难以抵挡其穿透性&#xff1b; 深夜还灯火通明的办公室工位上&#xff0c;浑浊的空气裹挟着疲惫的身体&#xff0…

Shell 通配符与正则表达元字符

Author&#xff1a;rab 目录 前言一、通配符1.1 *1.2 ?1.3 []1.4 {} 二、正则表达元字符2.1 *2.2 .2.3 ^2.4 $2.5 []2.6 \2.7 \<\>2.8 \{\} 总结 前言 不管是学任何语言&#xff0c;几乎都会涉及到通配符与正则的使用。有时候对于 Linux 初学者来说&#xff0c;往往会将…

Rust使用iced构建UI时,如何在界面显示中文字符

注&#xff1a;此文适合于对rust有一些了解的朋友 iced是一个跨平台的GUI库&#xff0c;用于为rust语言程序构建UI界面。 iced的基本逻辑是&#xff1a; UI交互产生消息message&#xff0c;message传递给后台的update&#xff0c;在这个函数中编写逻辑&#xff0c;然后通过…

轻松记录收支明细,一键打印,财务无忧!

作为现代人&#xff0c;管理好个人财务是非常重要的。但是&#xff0c;如何记录收支明细并打印出来呢&#xff1f;今天&#xff0c;我们向您推荐一款财务软件&#xff0c;帮助您轻松解决这个问题。 首先第一步&#xff0c;我们要打开【晨曦记账本】&#xff0c;并登录账号。 第…

Delphi 12 Athens 发布了!

官方安装包 ☞ https://altd.embarcadero.com/download/radstudio/12.0/RADStudio_12_0_4915718.iso 安装辅助工具、控件可以戳这里 &#xff1a;Delphi 12 资源 RAD Stuido 12 Athens &#xff0c;这次更新的细节还是比较多的&#xff0c;但主要还是多端&#xff08;iOS、An…

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

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

微服务 Spring Cloud 9,RPC框架,客户端和服务端如何建立网络连接?

目录 一、客户端和服务端如何建立网络连接&#xff1f;1、HTTP通信2、Socket通信 二、服务端如何处理请求&#xff1f;1、通常来说&#xff0c;有三种处理方式&#xff1a;2、不同的处理方式对应着不同的业务场景&#xff1a; 三、HTTP协议传输流程四、数据该如何序列化和反序列…

浅谈安科瑞智能照明系统在马来西亚国家石油公司项目的应用

摘要&#xff1a;随着社会经济的发展及网络技术、通信技术的提高&#xff0c;人们对照明设计提出了新的要求&#xff0c;它不仅要控制照明光源的发光时间、 亮度&#xff0c;而且与其它系统来配合不同的应用场合做出相应的灯光场景。本文介绍了马亚西亚石油公司智能照明项目的应…