• Home
  • About
    • 게임 개발자 유정룡 photo

      게임 개발자 유정룡

      포트폴리오

    • Learn More
    • Email
    • Github
    • Bitbucket
  • Projects
    • All Projects
    • All Tags

ProjectDark - 14 - EnemyWrath

14 Sep 2021

Reading time ~4 minutes

EnemyWrath

각 공격에 대한 정보를 저장할 구조체와, 현재 공격 타입을 결정할 enum을 만들어준다.

Wrath enum class, Struct
UENUM(BlueprintType)
enum class EEnemyWeaponType : uint8
{
	EWT_Sword UMETA(DisplayName = "Sword"),
	EWT_Fist UMETA(DisplayName = "Fist")
};

USTRUCT(Atomic, BlueprintType)
struct FAttackInfo
{
	GENERATED_USTRUCT_BODY()

public:
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Attack")
	class UAnimMontage* AttackMontage;
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Attack")
	TArray<int> Damages;
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Attack")
	float CoolTime;
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Attack")
	float CurrentCoolTime;
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Attack")
	float RestTime;
};

그리고 EnemyCharacterBase를 상속받아서 필요한 부분을 override하고,
각 부분의 변수를 만들어주자.

EnemyWrath.h
UCLASS()
class ENEMYCODES_API AEnemyWrath : public AEnemyCharacterBase
{
	GENERATED_BODY()
	
public:
	AEnemyWrath();

protected:
	void BeginPlay() override;

public:
	void Tick(float DeltaTime) override;

#pragma region Idle
private:
	virtual void IdleEnter() override;
	virtual void Idle() override;
	virtual void IdleExit() override;
#pragma endregion

#pragma region Targeting
private:
	virtual void TargetingEnter() override;
	virtual void Targeting() override;
	virtual void TargetingExit() override;

private:
	UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "Targeting", meta = (AllowPrivateAccess = true))
	class USphereComponent* TargetingCollider;

	FName TargetTag;

private:
	UFUNCTION()
	void BeginTargetingCollider(class UPrimitiveComponent* HitComp, class AActor* OtherActor, class UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult);
	UFUNCTION()
	void EndTargetingCollider(class UPrimitiveComponent* HitComp, class AActor* OtherActor, class UPrimitiveComponent* OtherComp, int32 OtherBodyIndex);

public:
	FORCEINLINE void SetTargetActor(AActor* Target){ TargetActor = Target; }
#pragma endregion

#pragma region Attack
private:
	virtual void AttackEnter() override;
	virtual void Attack() override;
	virtual void AttackExit() override;

private:
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Attack", meta = (AllowPrivateAccess = true))
	TArray<FAttackInfo> SwordCloseRangeAttackInfos;
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Attack", meta = (AllowPrivateAccess = true))
	TArray<FAttackInfo> SwordFarRangeAttackInfos;
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Attack", meta = (AllowPrivateAccess = true))
	TArray<FAttackInfo> FistAttackInfos;

	FAttackInfo* CurrentAttack;

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Attack", meta = (AllowPrivateAccess = true))
	float CloseRangeAttackDistance;
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Attack", meta = (AllowPrivateAccess = true))
	float FarRangeAttackDistance;

private:
	void CalAtkCooltimes(const float& DeltaTime);

	void SetAttackInfo();

	void SetAttackMontage(TArray<FAttackInfo>& Infos);


public:
#pragma endregion

#pragma region Dead
public:
	virtual void DeadEnter() override;
	virtual void Dead() override;
	virtual void DeadExit() override;
#pragma endregion

#pragma region Weapon
private:
	UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "Weapon", meta = (AllowPrivateAccess = true))
	EEnemyWeaponType CurrentWeaponType;
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Weapon", meta = (AllowPrivateAccess = true))
	class AEnemyWeaponBase* Weapon;

	UPROPERTY(EditAnywhere, blueprintReadWrite, Category = "Weapon", meta = (AllowPrivateAccess = true))
	class UAnimMontage* SummonWeaponMontage;

private:
	void AttachWeapon();

public:
	FORCEINLINE AEnemyWeaponBase* GetWeapon() const { return Weapon; }
#pragma endregion

#pragma region Combat
public:
	void BeginCombat(AActor* Target) override;
#pragma endregion

#pragma region CombatHitBox
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HitBox", meta = (AllowPrivateAccess = true))
	class UBoxComponent* LeftFistHitBox;
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HitBox", meta = (AllowPrivateAccess = true))
	class UBoxComponent* RightFistHitBox;


	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HitBox", meta = (AllowPrivateAccess = true))
	class UBoxComponent* LeftKickHitBox;
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HitBox", meta = (AllowPrivateAccess = true))
	class UBoxComponent* RightKickHitBox;
#pragma endregion

#pragma region Rest
private:
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Rest", meta = (AllowPrivateAccess = true))
	float RestTime;

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Rest", meta = (AllowPrivateAccess = true))
	float CurrentRestTime;

private:
	void Rest(const float& DeltaTime);
	void SetRestTime(const float& Time);
#pragma endregion
};

FAttackInfo 포인터 변수인 CurrentAttack을 계속해서 바꿔주면서 현재 상태에 따라 공격을 정하게 만들어준다.

공격은 쿨타임이 다 돌아 있는 것들 중에 랜덤으로 선택되며,
공격이 끝나면 RestTime동안 공격을 하지 않는다.

알단, 공격을 정하는 것들만 만들어두자.

EnemyWrath.cpp
// Fill out your copyright notice in the Description page of Project Settings.


#include "EnemyWrath.h"
#include "AIController.h"
#include "Components/SphereComponent.h"
#include "Components/BoxComponent.h"

#include "EnemyWeaponBase.h"

#include "MainPlayerCodes/Public/MainPlayer.h"

AEnemyWrath::AEnemyWrath()
{
	TargetingCollider = CreateDefaultSubobject<USphereComponent>(TEXT("TargetingCollider"));
	TargetingCollider->SetupAttachment(RootComponent);

	LeftFistHitBox = CreateDefaultSubobject<UBoxComponent>(TEXT("LeftFistHitBox"));
	LeftFistHitBox->AttachToComponent(GetMesh(), FAttachmentTransformRules::SnapToTargetIncludingScale, "LeftWeaponSocket");

	RightFistHitBox = CreateDefaultSubobject<UBoxComponent>(TEXT("RightFistHitBox"));
	RightFistHitBox->AttachToComponent(GetMesh(), FAttachmentTransformRules::SnapToTargetIncludingScale, "RightWeaponSocket");

	LeftKickHitBox = CreateDefaultSubobject<UBoxComponent>(TEXT("LeftKickHitBox"));
	LeftKickHitBox->AttachToComponent(GetMesh(), FAttachmentTransformRules::SnapToTargetIncludingScale, "LeftKickSocket");

	RightKickHitBox = CreateDefaultSubobject<UBoxComponent>(TEXT("RightKickHitBox"));
	RightKickHitBox->AttachToComponent(GetMesh(), FAttachmentTransformRules::SnapToTargetIncludingScale, "RightKickSocket");

	CurrentAttack = NULL;
}

void AEnemyWrath::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);

	CalAtkCooltimes(DeltaTime);
	Rest(DeltaTime);
}

void AEnemyWrath::AttackEnter()
{
	Super::AttackEnter();
	UE_LOG(LogTemp, Log, TEXT("AttackEnter"));
	SetAttackInfo();
}

void AEnemyWrath::Attack()
{
}

void AEnemyWrath::AttackExit()
{
	SetRestTime(CurrentAttack->RestTime);
}

void AEnemyWrath::CalAtkCooltimes(const float& DeltaTime)
{
	for (int i = 0; i < SwordCloseRangeAttackInfos.Num(); i++)
	{
		SwordCloseRangeAttackInfos[i].CurrentCoolTime += DeltaTime;
		if (SwordCloseRangeAttackInfos[i].CurrentCoolTime >= SwordCloseRangeAttackInfos[i].CoolTime)
		{
			SwordCloseRangeAttackInfos[i].CurrentCoolTime = SwordCloseRangeAttackInfos[i].CoolTime;
		}
	}

	for (int i = 0; i < SwordFarRangeAttackInfos.Num(); i++)
	{
		SwordFarRangeAttackInfos[i].CurrentCoolTime += DeltaTime;
		if (SwordFarRangeAttackInfos[i].CurrentCoolTime >= SwordFarRangeAttackInfos[i].CoolTime)
		{
			SwordFarRangeAttackInfos[i].CurrentCoolTime = SwordFarRangeAttackInfos[i].CoolTime;
		}
	}

	for (int i = 0; i < FistAttackInfos.Num(); i++)
	{
		FistAttackInfos[i].CurrentCoolTime += DeltaTime;
		if (FistAttackInfos[i].CurrentCoolTime >= FistAttackInfos[i].CoolTime)
		{
			FistAttackInfos[i].CurrentCoolTime = FistAttackInfos[i].CoolTime;
		}
	}
}

void AEnemyWrath::SetAttackInfo()
{
	switch (CurrentWeaponType)
	{
		case EEnemyWeaponType::EWT_Sword:
			if (TargetActor)
			{
				float Distance = FVector::Distance(TargetActor->GetActorLocation(), GetActorLocation());

				if (Distance < CloseRangeAttackDistance)
				{
					SetAttackMontage(SwordCloseRangeAttackInfos);
				}
				else if (Distance >= CloseRangeAttackDistance && Distance < FarRangeAttackDistance)
				{;
					SetAttackMontage(SwordFarRangeAttackInfos);
				}
				else
				{
					SetCurrentState(EEnemyState::ES_Targeting);
				}
			}
		break;
		case EEnemyWeaponType::EWT_Fist:;
			SetAttackMontage(FistAttackInfos);
		break;
	}
}

void AEnemyWrath::SetAttackMontage(TArray<FAttackInfo>& Infos)
{
	TArray<FAttackInfo*> SelectableAttacks;
	for (int i = 0; i < Infos.Num(); i++)
	{
		if (Infos[i].CurrentCoolTime >= Infos[i].CoolTime)
		{
			SelectableAttacks.Add(&Infos[i]);
		}
	}
	int RandomValue = FMath::RandRange(0, SelectableAttacks.Num() - 1);
	CurrentAttack = SelectableAttacks[RandomValue];
}

void AEnemyWrath::AttachWeapon()
{
	if (Weapon)
	{
		Weapon->AttachToCharacter(this);
	}
}

void AEnemyWrath::BeginCombat(AActor* Target)
{
	Super::BeginCombat(Target);

	if (SummonWeaponMontage)
	{
		UAnimInstance* AnimInstance = GetMesh()->GetAnimInstance();
		if (AnimInstance)
		{
			AnimInstance->Montage_Play(SummonWeaponMontage);
		}
	}

	CurrentWeaponType = EEnemyWeaponType::EWT_Sword;
}

void AEnemyWrath::SetRestTime(const float& Time)
{
	CurrentRestTime = 0.0f;
	RestTime = Time;
}

void AEnemyWrath::Rest(const float& DeltaTime)
{
	CurrentRestTime += DeltaTime;
	if (CurrentRestTime >= RestTime)
	{
		CurrentRestTime = RestTime;
	}
}

일단 함수 안에 아무것도 적지 않는 것들은 이 게시물에서 지웠다(너무 길다 ㅠㅠ)
Tick에서는 항상 쿨타임을 계산해주며, RestTime도 계산해준다.

다음에는 플레이어를 따라다니고 가까히 오면 공격하는 AI를 만들자.



Unreal Share Tweet +1