[UE5] 언리얼5 C++ - AI 부모클래스

AnJH·2024년 5월 30일

BehaviorTree_EnemyAttack_1

AI Controller를 가지는 여러 Enemy를 만들기 위해 Enemy의 부모클래스를 하나 만들도록 한다.

이전 글에서 만든 AI Enemy와 만드는 방식은 비슷하나 여러 객체들이 상속받아 사용할 수 있는 클래스를 만들고자 하는 것이다.

  • MainEnemy.h
#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "MainEnemy.generated.h"

DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnAttackEnd);

UCLASS()
class THELASTSURVIVOR_API AMainEnemy : public ACharacter
{
	GENERATED_BODY()

public:
	// Sets default values for this character's properties
	AMainEnemy();

protected:
	// Called when the game starts or when spawned
	virtual void BeginPlay() override;

	UFUNCTION()
	virtual void OnAttackMontageEnded(UAnimMontage* Montage, bool bInterrupted);

	UFUNCTION()
	virtual void AttackCheckOverlap(UPrimitiveComponent* OverlapComp, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult);

	virtual void EnemyDie();
	virtual void DestroyEnemy();

	float damage = 30;

	FTimerHandle DieTimerHandle;
	int32 dieDelay = 5;

	UPROPERTY(VisibleAnywhere, Category = Sound)
	class USoundBase* DieSound;

public:	
	// Called every frame
	virtual void Tick(float DeltaTime) override;

	// Called to bind functionality to input
	virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;

	virtual float TakeDamage(float DamageAmount, FDamageEvent const& DamageEvent, AController* EventInstigator, AActor* DamageCauser) override;

	FOnAttackEnd OnAttackEnd;

	UPROPERTY(EditAnywhere, Category = EnemyState)
	bool bEnemyDie = false;

	UPROPERTY(VisibleAnywhere, Category = Collision)
	class UBoxComponent* LHCollision;

	UPROPERTY(VisibleAnywhere, Category = Collision)
	class UBoxComponent* RHCollision;

	virtual void OnCollStart();
	virtual void OnCollEnd();

	UFUNCTION()
	virtual void Attack();
};

MainEnemy에서는 상속받을 클래스들이 사용하는 공통적인 변수, 함수들을 선언한다. 함수의 경우, 이를 오버라이드하여 사용할 수 있도록 virtual 키워드를 붙여서 선언해준다.

  • MainEnemy.CPP
#include "MainEnemy.h"
#include "MainAIController.h"
#include "Components/BoxComponent.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "Kismet/GameplayStatics.h"

// Sets default values
AMainEnemy::AMainEnemy()
{
 	// 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;

	AIControllerClass = AMainAIController::StaticClass();
	AutoPossessAI = EAutoPossessAI::PlacedInWorldOrSpawned;

	bUseControllerRotationYaw = false;
	GetCharacterMovement()->bUseControllerDesiredRotation = false;
	GetCharacterMovement()->bOrientRotationToMovement = true;
	GetCharacterMovement()->RotationRate = FRotator(0.0f, 480.0f, 0.0f);

	GetMesh()->GetOwner()->Tags.Add("Enemy");
	GetMesh()->SetRelativeLocationAndRotation(FVector(0, 0, -90), FRotator(0, -90, 0));
	GetCharacterMovement()->MaxWalkSpeed = 300.0f;

	LHCollision = CreateDefaultSubobject<UBoxComponent>(TEXT("LeftHand"));
	LHCollision->SetupAttachment(GetMesh());
	LHCollision->AttachToComponent(GetMesh(), FAttachmentTransformRules::KeepRelativeTransform, TEXT("LeftHand"));

	RHCollision = CreateDefaultSubobject<UBoxComponent>(TEXT("RightHand"));
	RHCollision->SetupAttachment(GetMesh());
	RHCollision->AttachToComponent(GetMesh(), FAttachmentTransformRules::KeepRelativeTransform, TEXT("RightHand"));
}

float AMainEnemy::TakeDamage(float DamageAmount, FDamageEvent const& DamageEvent, AController* EventInstigator, AActor* DamageCauser)
{
	float ActualDamage = Super::TakeDamage(DamageAmount, DamageEvent, EventInstigator, DamageCauser);
	return ActualDamage;
}

void AMainEnemy::OnAttackMontageEnded(UAnimMontage* Montage, bool bInterrupted)
{
	OnAttackEnd.Broadcast();

}

void AMainEnemy::Attack()
{
	RHCollision->OnComponentBeginOverlap.RemoveDynamic(this, &AMainEnemy::AttackCheckOverlap);
	RHCollision->OnComponentBeginOverlap.AddDynamic(this, &AMainEnemy::AttackCheckOverlap);
	LHCollision->OnComponentBeginOverlap.RemoveDynamic(this, &AMainEnemy::AttackCheckOverlap);
	LHCollision->OnComponentBeginOverlap.AddDynamic(this, &AMainEnemy::AttackCheckOverlap);

}

void AMainEnemy::AttackCheckOverlap(UPrimitiveComponent* OverlapComp, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
	UGameplayStatics::ApplyDamage(OtherActor, damage, GetController(), nullptr, NULL);
}

void AMainEnemy::EnemyDie()
{
	GetCharacterMovement()->SetMovementMode(MOVE_None);
}

void AMainEnemy::DestroyEnemy()
{
	Destroy();
}

void AMainEnemy::OnCollStart()
{
	RHCollision->SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics);
	LHCollision->SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics);
}

void AMainEnemy::OnCollEnd()
{
	RHCollision->SetCollisionEnabled(ECollisionEnabled::NoCollision);
	LHCollision->SetCollisionEnabled(ECollisionEnabled::NoCollision);
}

BeginPlay()나 Tick()과 같이 사용되지 않는 함수들은 따로 적지 않았다.

생성자에서는 상속받을 객체에 따라 가변적인 값이 아닌 바뀌지 않는 코드만 정의한다.

또한, AI Controller를 MainEnemy에서 정의함으로써 상속받는 모든 클래스가 동일한 AI Controller를 갖게 한다.

이후 함수 정의에서도 공통적으로 가지는 코드만 작성하고, 이를 자식 클래스에서 Super 키워드를 통해 실행할 수 있도록 한다.

0개의 댓글