// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "BaseEnemy.generated.h"
UCLASS()
class UEGAME_API ABaseEnemy : public ACharacter
{
GENERATED_BODY()
public:
// Sets default values for this character's properties
ABaseEnemy();
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "AI")
class USphereComponent* ChaseVolume;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "AI")
USphereComponent* AttackVolume;
protected:
// Called when the game starts or when spawned
virtual void BeginPlay() override;
public:
// Called every frame
virtual void Tick(float DeltaTime) override;
// Called to bind functionality to input
virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
UFUNCTION()
virtual void OnChaseVolumeOverlapBegin(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult);
UFUNCTION()
virtual void OnChaseVolumeOverlapEnd(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex);
UFUNCTION()
virtual void OnAttackVolumeOverlapBegin(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult);
UFUNCTION()
virtual void OnAttackVolumeOverlapEnd(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex);
};
// 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"
// 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;
//避免摄像机被敌人给阻挡
GetMesh()->SetCollisionResponseToChannel(ECollisionChannel::ECC_Camera, ECollisionResponse::ECR_Ignore);
GetCapsuleComponent()->SetCollisionResponseToChannel(ECollisionChannel::ECC_Camera, ECollisionResponse::ECR_Ignore);
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);
}
// 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);
}
// Called every frame
void ABaseEnemy::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
}
// 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)
{
}
void ABaseEnemy::OnChaseVolumeOverlapEnd(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex)
{
}
void ABaseEnemy::OnAttackVolumeOverlapBegin(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
}
void ABaseEnemy::OnAttackVolumeOverlapEnd(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex)
{
}
++ Nav Mesh Bounds Volume:导航网格
UENUM(BlueprintType)
enum class EEnemyMovementStatus :uint8
{
EEMS_Idle UMETA(DisplayName="Idle"),
EEMS_MoveToTarget UMETA(DisPlayName="MoveToTarget"),
EEMS_Attacking UMETA(DisPlayName="Attacking"),
EEMS_Dead UMETA(DisPlayName="Dead")
};
//--------------------------------------------------------------
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Enemy Stats")
EEnemyMovementStatus EnemyMovementStatus;
//--------------------------------------------------------------
void MoveToTarget(class AMainPlayer* Player);
#include "AIController.h"
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "AI")
class AAIController* AIController;
// 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;
//-----------------省略----------------------------------------------
//设置持有属性
AutoPossessAI = EAutoPossessAI::PlacedInWorldOrSpawned;
//初始化默认移动状态
EnemyMovementStatus = EEnemyMovementStatus::EEMS_Idle;
}
// Called when the game starts or when spawned
void ABaseEnemy::BeginPlay()
{
Super::BeginPlay();
//-----------------省略----------------------------------------------
//拿到Controller
AIController = Cast<AAIController>(GetController());
}
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)
{
MoveToTarget(Player);
}
}
}
FAIMoveRequest 是UE中的一个结构体,主要用于指定人工智能(AI)代理的移动请求
。其内部包含了一系列参数,如目标位置、到达速度、抵达距离、是否允许短路等等,这些参数都可以用来控制 AI 代理的移动行为。FAIMoveRequest MoveRequest;
MoveRequest.SetGoalActor(Player);//设置移动请求目标
MoveRequest.SetAcceptanceRadius(10.f); //设置移动半径
在UE中,FNavPathSharedPtr NavPath 表示一个智能指针,用于存储和管理导航路径
。智能指针是一个类模板,可以自动管理所指向对象的生命周期,避免了因忘记释放内存而导致的内存泄漏问题。FNavPathSharedPtr 是一个 shared_ptr 类型的智能指针,它指向的是一个 FNavPath 对象
。FNavPath 是 Unreal Engine 中的一个类,用于表示从起点到终点的一条导航路径。FNavPathSharedPtr NavPath;//会返回路径
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::OnChaseVolumeOverlapEnd(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex)
{
if (OtherActor)
{
AMainPlayer* Player = Cast<AMainPlayer>(OtherActor);
if (Player)
{
if (AIController)
{
//停止移动
AIController->StopMovement();
}
}
}
}
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "Animation/AnimInstance.h"
#include "EnemyAnimInstance.generated.h"
/**
*
*/
UCLASS()
class UEGAME_API UEnemyAnimInstance : public UAnimInstance
{
GENERATED_BODY()
public:
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Animation Properties")
float Speed;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Animation Properties")
class ABaseEnemy* Enemy;
virtual void NativeInitializeAnimation() override;
UFUNCTION(BlueprintCallable, Category = "Animaion Properties")
void UpDataAnimationProperties();
};
// Fill out your copyright notice in the Description page of Project Settings.
#include "EnemyAnimInstance.h"
#include "Animation/AnimInstance.h"
#include "Characters/Enemy/BaseEnemy.h"
void UEnemyAnimInstance::NativeInitializeAnimation()
{
Enemy = Cast<ABaseEnemy>(TryGetPawnOwner());
}
void UEnemyAnimInstance::UpDataAnimationProperties()
{
if (Enemy)
{
Enemy = Cast<ABaseEnemy>(TryGetPawnOwner());
}
if (Enemy)
{
FVector SpeedVector = Enemy->GetVelocity();
FVector PlanarSpeed = FVector(SpeedVector.X, SpeedVector.Y, 0.f);
Speed = PlanarSpeed.Size();
}
}
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Attack")
bool bAttackVolumeOverlap;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Attack")
class UAnimMontage* AttackMontage;
UFUNCTION(BlueprintCallable)
void MoveToTarget(class AMainPlayer* Player);
void AttackBegin();
UFUNCTION(BlueprintCallable)
void AttackEnd();
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)
{
MoveToTarget(Player);
}
}
}
void ABaseEnemy::OnChaseVolumeOverlapEnd(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex)
{
if (OtherActor)
{
AMainPlayer* Player = Cast<AMainPlayer>(OtherActor);
if (Player)
{
if (AIController)
{
//停止移动
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)
{
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;
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;
if (bAttackVolumeOverlap)
{
AttackBegin();
}
}
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Attack")
class ABaseEnemy* AttackTarget;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Attack")
TSubclassOf<ABaseEnemy> EnemyFilter;
//-----------------------------------------------------------------
void UpdataAttackTarget();
GetOverlappingActors
:它能够获取与指定组件相交的所有 Actors 列表,在使用时记得加上敌人类头文件,避免不知道EnemyFilter
void AMainPlayer::UpdataAttackTarget()
{
TArray<AActor*> OVerlappingActors;
GetOverlappingActors(OVerlappingActors,EnemyFilter);
}
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 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();
}
}
}
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Attack")
float InterpSpeed;
UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "Attack")
bool bInterpToEnemy;
InterpSpeed = 15.f;
bInterpToEnemy = false;
void AMainPlayer::AttackBegin()
{
if (!bIsAttacking)
{
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)
{
AttackKeyDown();
}
}
//进行转向插值
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);
}
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Attack")
float InterpSpeed;
UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "Attack")
bool bInterpToPlayer;
InterpSpeed = 15.f;
bInterpToPlayer = false;
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();
}
}
#include "Kismet/GameplayStatics.h",使用RInterpTo也要头文件:#include "Kismet/KismetMathLibrary.h"
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);
}
}
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "BaseEnemy.generated.h"
UENUM(BlueprintType)
enum class EEnemyMovementStatus :uint8
{
EEMS_Idle UMETA(DisplayName="Idle"),
EEMS_MoveToTarget UMETA(DisPlayName="MoveToTarget"),
EEMS_Attacking UMETA(DisPlayName="Attacking"),
EEMS_Dead UMETA(DisPlayName="Dead")
};
UCLASS()
class UEGAME_API ABaseEnemy : public ACharacter
{
GENERATED_BODY()
public:
// Sets default values for this character's properties
ABaseEnemy();
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "AI")
class USphereComponent* ChaseVolume;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "AI")
USphereComponent* AttackVolume;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "AI")
class AAIController* AIController;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Enemy Stats")
EEnemyMovementStatus EnemyMovementStatus;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Attack")
bool bAttackVolumeOverlap;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Attack")
class UAnimMontage* AttackMontage;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Attack")
float InterpSpeed;
UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "Attack")
bool bInterpToPlayer;
protected:
// Called when the game starts or when spawned
virtual void BeginPlay() override;
public:
// Called every frame
virtual void Tick(float DeltaTime) override;
// Called to bind functionality to input
virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
UFUNCTION()
virtual void OnChaseVolumeOverlapBegin(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult);
UFUNCTION()
virtual void OnChaseVolumeOverlapEnd(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex);
UFUNCTION()
virtual void OnAttackVolumeOverlapBegin(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult);
UFUNCTION()
virtual void OnAttackVolumeOverlapEnd(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex);
UFUNCTION(BlueprintCallable)
void MoveToTarget(class AMainPlayer* Player);
void AttackBegin();
UFUNCTION(BlueprintCallable)
void AttackEnd();
};
// 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"
// 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);
//避免摄像机被敌人给阻挡
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;
}
// 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);
//拿到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)
{
MoveToTarget(Player);
}
}
}
void ABaseEnemy::OnChaseVolumeOverlapEnd(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex)
{
if (OtherActor)
{
AMainPlayer* Player = Cast<AMainPlayer>(OtherActor);
if (Player)
{
if (AIController)
{
//停止移动
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();
}
}
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "MainPlayer.generated.h"
//声明移动状态枚举
UENUM(BlueprintType)
enum class EPlayerMovementStatus :uint8
{
EPMS_Normal UMETA(DisplayName = "Normal"),
EPMS_Sprinting UMETA(DisplayName = "Sprinting"),
EPMS_Dead UMETA(DisplayName = "Dead")
};
UENUM(BlueprintType)
enum class EPlayerStaminaStatus :uint8
{
EPSS_Normal UMETA(DisplayName = "Normal"),
EPSS_Exhausted UMETA(DisplayName = "Exhausted"),
EPSS_ExhaustedRecovering UMETA(DisplayName = "ExhaustedRecovering")
};
UCLASS()
class UEGAME_API AMainPlayer : public ACharacter
{
GENERATED_BODY()
public:
// Sets default values for this character's properties
AMainPlayer();
//新建一个SpringArm
UPROPERTY(visibleAnywhere,BlueprintReadOnly)
class USpringArmComponent* SpringArm;
//新建一个Camera
UPROPERTY(visibleAnywhere, BlueprintReadOnly)
class UCameraComponent* FollowCamera;
float BaseTurnRate; //使用键盘X转向的速率
float BaseLookUpRate; //使用键盘Y转向的速率
//主角状态
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Playe State")
float Health;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Playe State")
float MaxHealth;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Playe State")
float Stamina;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Playe State")
float MaxStamina;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Playe State")
float StaminaConsumeRate;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Playe State", meta = (ClampMin = 0, ClampMax = 1))
float ExhaustedStamina;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Playe State")
int Coins;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Player State")
float RunningSpeed;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Player State")
float SprintSpeed;
UPROPERTY(VisibleAnywhere,BlueprintReadWrite,Category="Player State")
EPlayerMovementStatus MovementStatus;
UPROPERTY(VisibleAnywhere,BlueprintReadWrite,Category="Player State")
EPlayerStaminaStatus StaminaStatus;
bool bLeftShiftDown;
//检测是否持剑
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Weapon")
bool bIsWeapon;
//正在装备的武器
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Weapon")
class AWeaponItem* EquipWeapon;
//正在重叠的武器
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Weapon")
AWeaponItem* OverlapWeapon;
bool bAttackKeyDown;//是否按下攻击键
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Animation")
bool bIsAttacking;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Animation")
class UAnimMontage* AttackMontage;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Attack")
class ABaseEnemy* AttackTarget;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Attack")
TSubclassOf<ABaseEnemy> EnemyFilter;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Attack")
float InterpSpeed;
UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "Attack")
bool bInterpToEnemy;
protected:
// Called when the game starts or when spawned
virtual void BeginPlay() override;
public:
// Called every frame
virtual void Tick(float DeltaTime) override;
// Called to bind functionality to input
virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
//重新Character类中的Jump方法
void Jump() override;
void MoveForward(float value);
void MoveRight(float value);
void Turn(float Value);
void LookUp(float Value);
void TurnRate(float Rate);
void LookUpRate(float Rate);
//改变状态
UFUNCTION(BlueprintCallable,Category="Player|State")
void AddHealth(float value);
UFUNCTION(BlueprintCallable, Category = "Player|State")
void AddStamina(float value);
UFUNCTION(BlueprintCallable, Category = "Player|State")
void AddCoin(float value);
//重写TakeDamage方法
float TakeDamage(float Damage, struct FDamageEvent const& DamageEvent, AController* EventInstigator, AActor* DamageCauser) override;
//短小精悍
FORCEINLINE void LeftShiftDown() { bLeftShiftDown = true; }
FORCEINLINE void LeftShiftUp() { bLeftShiftDown = false; }
void SetMovementStatus(EPlayerMovementStatus Status);
void InteractKeyDown();
void AttackKeyDown();
FORCEINLINE void AttackKeyUp() { bAttackKeyDown = false; }
void AttackBegin();
UFUNCTION(BlueprintCallable)
void AttackEnd();
void UpdataAttackTarget();
};
// 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);
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()
{
//继承父类的方法
Super::Jump();
}
void AMainPlayer::MoveForward(float value)
{
if (Controller != nullptr && value != 0.f && !(bIsAttacking))
{
//获取到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))
{
//获取到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)
{
AddControllerYawInput(Value);
}
}
void AMainPlayer::LookUp(float Value)
{
//UE_LOG(LogTemp, Warning, TEXT("%f"), GetControlRotation().Pitch);
//控制视角
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)
{
AddControllerYawInput(Value);
}
}
void AMainPlayer::LookUpRate(float Rate)
{
//要乘以一个DeltaTime这样就可以避免高帧底帧差值问题
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)
{
MovementStatus = Status;
//切换状态的时候改变移动速度
switch (MovementStatus)
{
case EPlayerMovementStatus::EPMS_Sprinting:
GetCharacterMovement()->MaxWalkSpeed = SprintSpeed;
break;
default:
GetCharacterMovement()->MaxWalkSpeed = RunningSpeed;
break;
}
}
void AMainPlayer::InteractKeyDown()
{
if (OverlapWeapon)
{
if (EquipWeapon)
{
//交换武器
EquipWeapon->UnEuip(this);
OverlapWeapon->Equip(this);
}
else
{
//装备武器
OverlapWeapon->Equip(this);
}
}
else
{
if (EquipWeapon)
{
//卸载武器
EquipWeapon->UnEuip(this);
}
}
}
void AMainPlayer::AttackKeyDown()
{
bAttackKeyDown = true;
if (bIsWeapon)
{
AttackBegin();
}
}
void AMainPlayer::AttackBegin()
{
if (!bIsAttacking)
{
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)
{
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;
}