敌人NPC机制
敌人机制分析与需求
新建一个character类来作为敌人,直接建蓝图设置骨骼网格,因为敌人可能多种就不规定死,然后这个敌人肯定需要两个触发器,一个用于大范围巡逻,一个用于是否达到主角近点进行攻击 注意我们要避免摄像机被敌人阻挡 BaseEnemy.h
# pragma once
# include "CoreMinimal.h"
# include "GameFramework/Character.h"
# include "BaseEnemy.generated.h"
UCLASS ( )
class UEGAME_API ABaseEnemy : public ACharacter
{
GENERATED_BODY ( )
public :
ABaseEnemy ( ) ;
UPROPERTY ( VisibleAnywhere, BlueprintReadOnly, Category = "AI" )
class USphereComponent * ChaseVolume;
UPROPERTY ( VisibleAnywhere, BlueprintReadOnly, Category = "AI" )
USphereComponent* AttackVolume;
protected :
virtual void BeginPlay ( ) override ;
public :
virtual void Tick ( float DeltaTime) override ;
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) ;
} ;
# include "BaseEnemy.h"
# include "Components/SphereComponent.h"
# include "Components/SkeletalMeshComponent.h"
# include "Components/CapsuleComponent.h"
ABaseEnemy :: ABaseEnemy ( )
{
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) ;
}
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) ;
}
void ABaseEnemy :: Tick ( float DeltaTime)
{
Super :: Tick ( DeltaTime) ;
}
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)
{
}
注意虚幻的visibility与camera默认是block
部署导航网格
Nav Mesh Bounds Volume:导航网格 Nav Modifier Volume:修改导航网格
添加AI模块与创建敌人移动状态枚举
有些组件的使用是需要添加依赖项的就像之前的UMG需要添加到自己的工程目录下的工程名.Build.cs
里面,调用AI的模块就需要添加AIModule
添加敌人移动状态枚举变量与接近主角的函数
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) ;
获取AIController并持有敌人
AAIcontroller* AIController所需头文件:#include "AIController.h"
声明AAIController类的指针
UPROPERTY ( VisibleAnywhere, BlueprintReadOnly, Category = "AI" )
class AAIController * AIController;
ABaseEnemy :: ABaseEnemy ( )
{
PrimaryActorTick. bCanEverTick = true ;
AutoPossessAI = EAutoPossessAI:: PlacedInWorldOrSpawned;
EnemyMovementStatus = EEnemyMovementStatus:: EEMS_Idle;
}
void ABaseEnemy :: BeginPlay ( )
{
Super :: BeginPlay ( ) ;
AIController = Cast < AAIController> ( GetController ( ) ) ;
}
调用MoveTo去追逐Player
逻辑(一):首先在追逐事件中去判断是不是Player如果是就调用追逐函数MoveToTarget
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 :: 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 ( ) ;
}
}
}
}
创建敌人动画蓝图
基本与创建MainPlayer动画蓝图差不多 EnemyAnimInstance,h
# 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 ( ) ;
} ;
# 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 ( ) ;
}
}
创建动画蓝图
敌人行走的混合空间
基本和当时创建Player的混合空间差不多 编写动画蓝图 然后设置到蓝图上即可
创建蒙太奇以及攻击编码
创建蒙太奇 攻击逻辑:老规矩引用一个bool变量用于检测是否在攻击范围,新建Montage引用后续方便调用Montage功能,与两个函数用来攻击和攻击结束的逻辑编写,攻击结束要在蓝图中调用加上反射,在重叠事件中写是否检测到主角进入攻击范围如果是就执行攻击函数,攻击函数中首先关闭移动,然后判断是不是正在攻击(默认肯定没有攻击,所以判断完要设定为正在攻击),然后获取AnimInstance进行片段播放,攻击结束函数逻辑就先把状态变为待机状态,然后判断是否在攻击范围bool变量,如果为真就继续执行攻击函数形成闭环,最后离开了重叠事件范围就先把bool检测攻击变为false,判断攻击状态是否结束,如果结束就继续执行追逐主角函数,可能这个逻辑会导致bug,所以我们把追逐主角的函数MoveToTarget添加反射到时候在蓝图中完善 需要的变量与函数
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 ( ) ;
}
}
攻击动画的编写以及连续追逐
首先把Montage的攻击通知加上,然后在动画蓝图里面添加蒙太奇 然后在事件图表里面进行编辑,调用通知AttackEnd事件执行AttackEnd函数,然后进行判断主角离开了攻击重叠事件的判断,如果离开了,就又继续执行追逐主角函数
敌人更新攻击目标
思想:近点跟随,谁离得近就先攻击谁 在MainPlayer中新建一个敌人类的指针引用与一个模版敌人类,一个更新攻击目标的函数
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) ;
}
在返回的相交所有Actor列表里面进行选择最近的那个,逻辑是:遍历OverlapingActors数组进行比较,近的就替换远的。新建一个用于交换的敌人类引用,新建一个最小范围,然后获取当前位置,然后开始变量数组,将数组里面的单位全部转换为敌人类,判断敌人是否存在是否死亡,如果存在无死亡就当前距离主角的位置记录下来,如果距离主角位置小于最小范围那么就将距离主角位置赋值最小范围,将当前敌人类给交换敌人的引用,循环结束就将当前距离主角位置赋值给AttackTarget
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 ( ) ;
if ( DistanceToActor < MinDistance)
{
MinDistance = DistanceToActor;
ClosestDistance = Enemy;
}
}
}
AttackTarget = ClosestDistance;
}
然后在BaseEnemy类的攻击碰撞事件中调用此函数
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 ( ) ;
}
}
}
玩家自动面向攻击目标
采用插值的思想方法,当我们需要攻击转向时就进行插值转向 在MainPlayer类中新建一个插值速度变量与一个bool是否进行插值变量并赋初值
UPROPERTY ( EditAnywhere, BlueprintReadWrite, Category = "Attack" )
float InterpSpeed;
UPROPERTY ( VisibleAnywhere, BlueprintReadWrite, Category = "Attack" )
bool bInterpToEnemy;
InterpSpeed = 15.f ;
bInterpToEnemy = false ;
然后在攻击函数中将bool插值变量赋为true,攻击结束函数中设为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 ( ) ;
}
}
然后去Tick里面进行转向逻辑编写,使用RInterpTo要头文件:#include “Kismet/KismetMathLibrary.h”
if ( bInterpToEnemy && AttackTarget)
{
FRotator LookAtYaw ( 0.f , UKismetMathLibrary :: FindLookAtRotation ( GetActorLocation ( ) , AttackTarget-> GetActorLocation ( ) ) . Yaw, 0.f ) ;
FRotator InterpRotation = FMath :: RInterpTo ( GetActorRotation ( ) , LookAtYaw, DeltaTime, InterpSpeed) ;
SetActorRotation ( InterpRotation) ;
}
将Enemy给过滤器,必须是C++类
敌人自动面向玩家攻击目标
UPROPERTY ( EditAnywhere, BlueprintReadWrite, Category = "Attack" )
float InterpSpeed;
UPROPERTY ( VisibleAnywhere, BlueprintReadWrite, Category = "Attack" )
bool bInterpToPlayer;
InterpSpeed = 15.f ;
bInterpToPlayer = false ;
然后在攻击函数中将bool插值变量赋为true,攻击结束函数中设为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 ( ) ;
}
}
然后去Tick里面进行转向逻辑编写,注意的事FindLookAtRotation中的目标位置是Player,得去获取位置,获取位置要加头文件:#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) ;
}
}
BaseEnemy.h
# 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 :
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 :
virtual void BeginPlay ( ) override ;
public :
virtual void Tick ( float DeltaTime) override ;
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 ( ) ;
} ;
BaseEnemy.cpp
# 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"
ABaseEnemy :: ABaseEnemy ( )
{
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 ;
}
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) ;
AIController = Cast < AAIController> ( GetController ( ) ) ;
}
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) ;
}
}
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 ( ) ;
}
}
MainPlayer.h
# 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 :
AMainPlayer ( ) ;
UPROPERTY ( visibleAnywhere, BlueprintReadOnly)
class USpringArmComponent * SpringArm;
UPROPERTY ( visibleAnywhere, BlueprintReadOnly)
class UCameraComponent * FollowCamera;
float BaseTurnRate;
float BaseLookUpRate;
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 :
virtual void BeginPlay ( ) override ;
public :
virtual void Tick ( float DeltaTime) override ;
virtual void SetupPlayerInputComponent ( class UInputComponent * PlayerInputComponent) override ;
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) ;
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 ( ) ;
} ;
MainPlayer.cpp
# 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"
AMainPlayer :: AMainPlayer ( )
{
PrimaryActorTick. bCanEverTick = true ;
SpringArm = CreateDefaultSubobject < USpringArmComponent> ( TEXT ( "SpringArm" ) ) ;
SpringArm-> SetupAttachment ( GetRootComponent ( ) ) ;
SpringArm-> TargetArmLength = 600.f ;
SpringArm-> bUsePawnControlRotation = true ;
FollowCamera = CreateDefaultSubobject < UCameraComponent> ( TEXT ( "FollowCamera" ) ) ;
FollowCamera-> SetupAttachment ( SpringArm, NAME_None) ;
FollowCamera-> bUsePawnControlRotation = false ;
GetCapsuleComponent ( ) -> SetCapsuleSize ( 35.f , 100.f ) ;
bUseControllerRotationPitch = false ;
bUseControllerRotationYaw = false ;
bUseControllerRotationRoll = false ;
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;
bLeftShiftDown = false ;
InterpSpeed = 15.f ;
bInterpToEnemy = false ;
}
void AMainPlayer :: BeginPlay ( )
{
Super :: BeginPlay ( ) ;
}
void AMainPlayer :: Tick ( float DeltaTime)
{
Super :: Tick ( DeltaTime) ;
switch ( StaminaStatus)
{
case EPlayerStaminaStatus:: EPSS_Normal:
if ( bLeftShiftDown)
{
if ( Stamina - StaminaConsumeRate * DeltaTime <= MaxStamina * ExhaustedStamina)
{
StaminaStatus = EPlayerStaminaStatus:: EPSS_Exhausted;
}
Stamina -= StaminaConsumeRate * DeltaTime;
SetMovementStatus ( EPlayerMovementStatus:: EPMS_Sprinting) ;
}
else
{
Stamina = FMath :: Clamp ( Stamina + StaminaConsumeRate * DeltaTime, 0.f , MaxStamina) ;
SetMovementStatus ( EPlayerMovementStatus:: EPMS_Normal) ;
}
break ;
case EPlayerStaminaStatus:: EPSS_Exhausted:
if ( bLeftShiftDown)
{
if ( Stamina - StaminaConsumeRate * DeltaTime <= 0.f )
{
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:
if ( Stamina + StaminaConsumeRate * DeltaTime >= MaxStamina * ExhaustedStamina)
{
StaminaStatus = EPlayerStaminaStatus:: EPSS_Normal;
}
Stamina += StaminaConsumeRate * DeltaTime;
LeftShiftUp ( ) ;
SetMovementStatus ( EPlayerMovementStatus:: EPMS_Normal) ;
break ;
default :
break ;
}
if ( bInterpToEnemy && AttackTarget)
{
FRotator LookAtYaw ( 0.f , UKismetMathLibrary :: FindLookAtRotation ( GetActorLocation ( ) , AttackTarget-> GetActorLocation ( ) ) . Yaw, 0.f ) ;
FRotator InterpRotation = FMath :: RInterpTo ( GetActorRotation ( ) , LookAtYaw, DeltaTime, InterpSpeed) ;
SetActorRotation ( InterpRotation) ;
}
}
void AMainPlayer :: SetupPlayerInputComponent ( UInputComponent* PlayerInputComponent)
{
Super :: SetupPlayerInputComponent ( PlayerInputComponent) ;
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) ;
PlayerInputComponent-> BindAction ( "Sprint" , IE_Released, this , & AMainPlayer:: LeftShiftUp) ;
PlayerInputComponent-> BindAction ( "Interact" , IE_Pressed, this , & AMainPlayer:: InteractKeyDown) ;
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) ;
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) )
{
FRotator Rotation = Controller-> GetControlRotation ( ) ;
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) )
{
FRotator Rotation = Controller-> GetControlRotation ( ) ;
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)
{
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)
{
float Value = Rate * BaseTurnRate * GetWorld ( ) -> GetDeltaSeconds ( ) ;
if ( Value != 0.f )
{
AddControllerYawInput ( Value) ;
}
}
void AMainPlayer :: LookUpRate ( float Rate)
{
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) ;
}
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 ( ) ;
if ( DistanceToActor < MinDistance)
{
MinDistance = DistanceToActor;
ClosestDistance = Enemy;
}
}
}
AttackTarget = ClosestDistance;
}