문제: 적 AI 시스템 구현
요구사항
- 추상 클래스 설계
- AEnemyBase라는 추상 클래스를 생성합니다.
- 이 클래스는 다음과 같은 순수 가상 함수(Pure Virtual Functions)를 가집니다:
- Move(): 적이 이동하는 기능을 정의합니다.
- Attack(): 적이 기본 공격을 수행하는 기능입니다.
- TriggerRandomEvent(): 랜덤 이벤트를 실행하는 기능입니다.
- 공격 주기 설정
- Attack은 적이 플레이어와 일정 거리 안에 있을 때 2초마다 주기적으로 호출됩니다.
- 공격 시 30% 확률로 TriggerRandomEvent가 호출됩니다.
- 플레이어와의 거리 계산
- 적은 플레이어와의 거리를 계산하여 행동을 결정합니다:
- 500 유닛 이하: 공격 루프를 시작합니다.
- 500 유닛 초과: 이동 상태로 전환하고 공격 루프를 중지합니다.
- 적은 플레이어와의 거리를 계산하여 행동을 결정합니다:
- 인터페이스 활용
- IAttackPattern 인터페이스를 생성하여 모든 적이 공격 패턴을 정의하도록 합니다.
- AEnemyBase 클래스는 IAttackPattern을 구현합니다.
- 파생 클래스 구현
- AFastEnemy: 빠르게 이동하며 공격 속도가 빠른 적 클래스.
- AStrongEnemy: 느리게 이동하지만 강력한 공격을 하는 적 클래스.
- 두 클래스는 각각 Move, Attack, TriggerRandomEvent를 오버라이드합니다.
- 적 관리 클래스
- AEnemyManager 클래스를 만들어 적을 관리합니다.
- SpawnEnemies() 함수로 여러 적을 생성하고, ManageEnemies(FVector PlayerLocation)로 적의 행동을 제어합니다.
전체 구조
- AttackPattern 인터페이스를 만들어 EnemyBase가 상속 받는다.
- EnemyBase는 인터페이스의 ExecuteAttack 함수를 오버라이드해서 필수적으로 구현한다.
- EnemyBase는 Move, Attack, TrggerRandomEvent 의 순수 가상함수를 가지고 있게하여 추상클래스로 처리한다.
- CalculateDistance 함수로 AI와 Player의 거리에 따라 이동하거나 공격한다.
- 공격 범위 내에 있다면 StartAttackLoop 함수로 2초마다 공격하고 30퍼센트 확률로 각 객체 고유의 스킬을 사용한다.
- EnemyBase의 파생클래스, FastEnemy와 StrongEnemy는 EnemyBase의 순수 가상함수를 필수적으로 구현하고 이에 따라 각 객체는 서로 다른 이동 속도, 공격 속도, 스킬을 가진다.
- EnemyManager는 다형성을 사용해 ENemyBase 클래스로 파생 클래스 FastEnemy와 StrongEnemy를 관리할 수 있게하고 스폰된 객체를 배열로 한꺼번에 관리할 수 있게 하며, 행동을 제어한다.
*AttackPattern
더보기
.h
#pragma once
#include "CoreMinimal.h"
#include "UObject/Interface.h"
#include "AttackPattern.generated.h"
UINTERFACE(MinimalAPI)
class UAttackPattern : public UInterface
{
GENERATED_BODY()
};
class BASIS_API IAttackPattern
{
GENERATED_BODY()
public:
virtual void ExecuteAttack() = 0;
};
*EnemytBase
더보기
.h
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "AttackPattern.h"
#include "EnemyBase.generated.h"
UCLASS(Abstract, NotBlueprintable)
class BASIS_API AEnemyBase : public AActor, public IAttackPattern
{
GENERATED_BODY()
public:
AEnemyBase();
virtual ~AEnemyBase() = default;
virtual void ExecuteAttack() override;
void CalculateDistance(const FVector& PlayerLocation);
protected:
virtual void Move() PURE_VIRTUAL(AEnemyBase::Move, );
virtual void Attack() PURE_VIRTUAL(AEnemyBase::Attack, );
virtual void TriggerRandomEvent() PURE_VIRTUAL(AEnemyBase::TriggerRandomEvent, );
void PerformAttack();
void StartAttackLoop();
FVector CurrentLocation;
float MoveSpeed;
float AttackInterval;
FTimerHandle AttackTimerHandle;
};
.cpp
#include "EnemyBase.h"
#include "Math/UnrealMathUtility.h"
// Sets default values
AEnemyBase::AEnemyBase()
{
CurrentLocation = FVector::ZeroVector;
MoveSpeed = 100.f;
AttackInterval = 2.0f;
}
void AEnemyBase::ExecuteAttack()
{
UE_LOG(LogTemp, Warning, TEXT("Base Enemy Attack Pattern!"));
}
void AEnemyBase::PerformAttack()
{
Attack();
if (FMath::RandRange(0, 100) < 30)
{
TriggerRandomEvent();
}
}
void AEnemyBase::StartAttackLoop()
{
ExecuteAttack();
GetWorld()->GetTimerManager().SetTimer(AttackTimerHandle, this, &AEnemyBase::PerformAttack, AttackInterval, true, 0.5f);
}
void AEnemyBase::CalculateDistance(const FVector& PlayerLocation)
{
CurrentLocation = GetActorLocation();
float Distance = FVector::Dist(CurrentLocation, PlayerLocation);
if (Distance < 500.f)
{
StartAttackLoop();
}
else
{
GetWorld()->GetTimerManager().ClearTimer(AttackTimerHandle);
Move();
}
}
*FastEnemy
더보기
.h
#pragma once
#include "CoreMinimal.h"
#include "EnemyBase.h"
#include "FastEnemy.generated.h"
UCLASS()
class BASIS_API AFastEnemy : public AEnemyBase
{
GENERATED_BODY()
public:
AFastEnemy();
virtual void ExecuteAttack() override;
protected:
virtual void Move() override;
virtual void Attack() override;
virtual void TriggerRandomEvent() override;
};
.cpp
#include "FastEnemy.h"
AFastEnemy::AFastEnemy()
{
MoveSpeed = 200.f;
AttackInterval = 1.2f;
}
void AFastEnemy::ExecuteAttack()
{
UE_LOG(LogTemp, Warning, TEXT("Fast enemy executes a quick attack!"));
}
void AFastEnemy::Move()
{
UE_LOG(LogTemp, Warning, TEXT("Fast enemy is moving quickly at speed: %f"), MoveSpeed);
}
void AFastEnemy::Attack()
{
UE_LOG(LogTemp, Warning, TEXT("Fast enemy attacks rapidly!"));
}
void AFastEnemy::TriggerRandomEvent()
{
UE_LOG(LogTemp, Warning, TEXT("Fast enemy activated speed boost!"));
}
*StrongEnemy
더보기
.h
#pragma once
#include "CoreMinimal.h"
#include "EnemyBase.h"
#include "StrongEnemy.generated.h"
UCLASS()
class BASIS_API AStrongEnemy : public AEnemyBase
{
GENERATED_BODY()
public:
AStrongEnemy();
virtual void ExecuteAttack() override;
protected:
virtual void Move() override;
virtual void Attack() override;
virtual void TriggerRandomEvent() override;
};
.cpp
#include "StrongEnemy.h"
AStrongEnemy::AStrongEnemy()
{
MoveSpeed = 80.f;
AttackInterval = 3.5f;
}
void AStrongEnemy::ExecuteAttack()
{
UE_LOG(LogTemp, Warning, TEXT("String enemy execute a powerfull attack!"));
}
void AStrongEnemy::Move()
{
UE_LOG(LogTemp, Warning, TEXT("Strong enemy is moving slowly at speed: %f"), MoveSpeed);
}
void AStrongEnemy::Attack()
{
UE_LOG(LogTemp, Warning, TEXT("String enemy attacks powerfully!"));
}
void AStrongEnemy::TriggerRandomEvent()
{
UE_LOG(LogTemp, Warning, TEXT("String enemy activated defense amour!"));
}
*EnemyManager
더보기
.h
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "EnemyManager.generated.h"
class AEnemyBase;
class AFastEnemy;
class AStrongEnemy;
UCLASS()
class BASIS_API AEnemyManager : public AActor
{
GENERATED_BODY()
public:
AEnemyManager() : EnemiesCnt(10) {}
AEnemyManager(const int32& Cnt) : EnemiesCnt(Cnt) {}
void SpawnEnemis();
void ManageEnemies(FVector PlayerLocation);
private:
TArray<AEnemyBase*> Enemies;
int32 EnemiesCnt;
};
.cpp
#include "EnemyManager.h"
#include "EnemyBase.h"
#include "FastEnemy.h"
#include "StrongEnemy.h"
#include "cmath"
#include "Engine/World.h"
void AEnemyManager::SpawnEnemis()
{
FActorSpawnParameters SpawnParams;
SpawnParams.Owner = this;
int32 StrongEnemyCnt = sqrt(EnemiesCnt);
for (int i = 0; i < EnemiesCnt; ++i)
{
if (i < StrongEnemyCnt)
{
AEnemyBase* Enemy = GetWorld()->SpawnActor<AStrongEnemy>(AStrongEnemy::StaticClass(), FVector::ZeroVector, FRotator::ZeroRotator, SpawnParams);
if (Enemy)
{
UE_LOG(LogTemp, Warning, TEXT("Strong Enemy Spawned!"));
Enemies.Add(Enemy);
}
}
else
{
AEnemyBase* Enemy = GetWorld()->SpawnActor<AFastEnemy>(AFastEnemy::StaticClass(), FVector::ZeroVector, FRotator::ZeroRotator, SpawnParams);
if (Enemy)
{
UE_LOG(LogTemp, Warning, TEXT("Fast Enemy Spawned!"));
Enemies.Add(Enemy);
}
}
}
}
void AEnemyManager::ManageEnemies(FVector PlayerLocation)
{
for (AEnemyBase* Enemy : Enemies)
{
Enemy->CalculateDistance(PlayerLocation);
}
}
트러블 슈팅
- EnemyBase에서 순수 가상함수를 선언하면 위와 같은 오류 발생
오류 상황을 요약하면, 언리얼엔진에서 CDO(Create Default Object)를 생성하는 과정에서 해당 클래스는 모든 함수가 실행 가능한 상태여야 하지만, EnemyBase의 순수 가상함수는 구현부가 존재하지 않으므로 발생하는 문제였다. 오류 과정과 해결방법은 아래 링크에 정리하였다.
https://dong-grae.tistory.com/105
25.01.08 (수)
언리얼 엔진에서 순수 가상함수 선언#pragma once#include "CoreMinimal.h"#include "GameFramework/Actor.h"#include "AttackPattern.h"#include "EnemyBase.generated.h"UCLASS(Abstract, NotBlueprintable)class BASIS_API AEnemyBase : public AActor, pub
dong-grae.tistory.com
'내배캠 > Unreal Engine' 카테고리의 다른 글
정적 초기화와 런타임 로드 (0) | 2025.01.20 |
---|---|
빌드 문제 복구하기 (0) | 2025.01.20 |
TArray (0) | 2025.01.06 |
Event Dispatcher (0) | 2024.12.22 |
블루프린트로 레벨디자인 만들기 (1) | 2024.12.22 |