icon안동민 개발노트

AI 컨트롤러 기본 구현


 언리얼 엔진의 AI 시스템은 게임 내 인공지능 캐릭터를 구현하는 강력한 도구를 제공합니다. 이 절에서는 C++를 사용하여 AI 컨트롤러를 구현하는 방법을 살펴보겠습니다.

AAIController 클래스

 AAIController는 AI 캐릭터의 행동을 제어하는 기본 클래스입니다.

UCLASS()
class AMyAIController : public AAIController
{
    GENERATED_BODY()
 
public:
    AMyAIController();
 
    virtual void BeginPlay() override;
    virtual void Tick(float DeltaTime) override;
 
    // AI 행동 함수들
    void MoveToRandomLocation();
    void RotateTowardsTarget(AActor* Target);
 
private:
    // AI 상태 변수들
    UPROPERTY()
    AActor* CurrentTarget;
 
    UPROPERTY()
    FVector TargetLocation;
};
 
AMyAIController::AMyAIController()
{
    PrimaryActorTick.bCanEverTick = true;
}
 
void AMyAIController::BeginPlay()
{
    Super::BeginPlay();
    MoveToRandomLocation();
}
 
void AMyAIController::Tick(float DeltaTime)
{
    Super::Tick(DeltaTime);
 
    if (CurrentTarget)
    {
        RotateTowardsTarget(CurrentTarget);
    }
}

AI 폰 생성 및 제어

 AI 폰을 생성하고 AI 컨트롤러와 연결하는 방법

void AMyGameMode::SpawnAIPawn()
{
    UWorld* World = GetWorld();
    if (World)
    {
        FActorSpawnParameters SpawnParams;
        SpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AdjustIfPossibleButAlwaysSpawn;
 
        ACharacter* AIPawn = World->SpawnActor<ACharacter>(ACharacter::StaticClass(), SpawnLocation, SpawnRotation, SpawnParams);
        if (AIPawn)
        {
            AMyAIController* AIController = World->SpawnActor<AMyAIController>(AMyAIController::StaticClass(), SpawnParams);
            if (AIController)
            {
                AIController->Possess(AIPawn);
            }
        }
    }
}

기본적인 AI 행동 구현

 이동

void AMyAIController::MoveToRandomLocation()
{
    UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent<UNavigationSystemV1>(GetWorld());
    if (NavSys)
    {
        FNavLocation RandomLocation;
        if (NavSys->GetRandomReachablePointInRadius(GetPawn()->GetActorLocation(), 1000.0f, RandomLocation))
        {
            MoveToLocation(RandomLocation.Location);
        }
    }
}

 회전

void AMyAIController::RotateTowardsTarget(AActor* Target)
{
    if (Target && GetPawn())
    {
        FVector Direction = Target->GetActorLocation() - GetPawn()->GetActorLocation();
        FRotator NewRotation = Direction.Rotation();
        NewRotation.Pitch = 0.0f;
        NewRotation.Roll = 0.0f;
        GetPawn()->SetActorRotation(NewRotation);
    }
}

 목표 추적

void AMyAIController::SetTarget(AActor* NewTarget)
{
    CurrentTarget = NewTarget;
    if (CurrentTarget)
    {
        MoveToActor(CurrentTarget);
    }
}

AI 인식 시스템 구현

 시야 구현

UCLASS()
class AMyAIController : public AAIController
{
    // ...
 
    UPROPERTY(VisibleAnywhere, Category = "AI")
    class UPawnSensingComponent* PawnSensingComp;
 
    UFUNCTION()
    void OnPawnSeen(APawn* SeenPawn);
};
 
AMyAIController::AMyAIController()
{
    PawnSensingComp = CreateDefaultSubobject<UPawnSensingComponent>(TEXT("PawnSensingComp"));
    PawnSensingComp->SetPeripheralVisionAngle(60.0f);
    PawnSensingComp->SightRadius = 1000.0f;
}
 
void AMyAIController::BeginPlay()
{
    Super::BeginPlay();
    PawnSensingComp->OnSeePawn.AddDynamic(this, &AMyAIController::OnPawnSeen);
}
 
void AMyAIController::OnPawnSeen(APawn* SeenPawn)
{
    if (SeenPawn && SeenPawn->IsPlayerControlled())
    {
        SetTarget(SeenPawn);
    }
}

 청각 구현

UCLASS()
class AMyAIController : public AAIController
{
    // ...
 
    UFUNCTION()
    void OnNoiseHeard(APawn* NoiseInstigator, const FVector& Location, float Volume);
};
 
void AMyAIController::BeginPlay()
{
    Super::BeginPlay();
    PawnSensingComp->OnHearNoise.AddDynamic(this, &AMyAIController::OnNoiseHeard);
}
 
void AMyAIController::OnNoiseHeard(APawn* NoiseInstigator, const FVector& Location, float Volume)
{
    if (NoiseInstigator && NoiseInstigator->IsPlayerControlled())
    {
        MoveToLocation(Location);
    }
}

AI 상태 관리

 간단한 상태 머신을 구현하여 AI의 행동을 관리할 수 있습니다.

UENUM(BlueprintType)
enum class EAIState : uint8
{
    Idle,
    Patrolling,
    Chasing,
    Attacking
};
 
UCLASS()
class AMyAIController : public AAIController
{
    // ...
 
    UPROPERTY(VisibleAnywhere, Category = "AI")
    EAIState CurrentState;
 
    void UpdateAIState();
};
 
void AMyAIController::UpdateAIState()
{
    switch (CurrentState)
    {
        case EAIState::Idle:
            // 대기 상태 로직
            break;
        case EAIState::Patrolling:
            // 순찰 상태 로직
            break;
        case EAIState::Chasing:
            // 추적 상태 로직
            break;
        case EAIState::Attacking:
            // 공격 상태 로직
            break;
    }
}

플레이어와의 상호작용

 플레이어와 AI의 상호작용을 구현하는 예

void AMyAIController::OnPawnSeen(APawn* SeenPawn)
{
    if (SeenPawn && SeenPawn->IsPlayerControlled())
    {
        CurrentState = EAIState::Chasing;
        SetTarget(SeenPawn);
    }
}
 
void AMyAIController::AttackPlayer()
{
    if (CurrentTarget && CurrentState == EAIState::Chasing)
    {
        float DistanceToTarget = FVector::Distance(GetPawn()->GetActorLocation(), CurrentTarget->GetActorLocation());
        if (DistanceToTarget <= AttackRange)
        {
            CurrentState = EAIState::Attacking;
            // 공격 로직 구현
        }
    }
}

AI 시스템의 성능 최적화 전략

  1. 틱 최적화
void AMyAIController::Tick(float DeltaTime)
{
    Super::Tick(DeltaTime);
 
    TickCounter += DeltaTime;
    if (TickCounter >= TickInterval)
    {
        PerformAILogic();
        TickCounter = 0.0f;
    }
}
  1. LOD (Level of Detail) 시스템 구현
void AMyAIController::UpdateAILOD()
{
    float DistanceToPlayer = FVector::Distance(GetPawn()->GetActorLocation(), PlayerPawn->GetActorLocation());
    if (DistanceToPlayer < NearLODDistance)
    {
        // 높은 수준의 AI 로직 실행
    }
    else if (DistanceToPlayer < MediumLODDistance)
    {
        // 중간 수준의 AI 로직 실행
    }
    else
    {
        // 낮은 수준의 AI 로직 실행
    }
}

디버깅 기법

  1. 시각적 디버깅
void AMyAIController::DrawDebugInfo()
{
    if (GetPawn())
    {
        FVector Location = GetPawn()->GetActorLocation();
        DrawDebugSphere(GetWorld(), Location, 50.0f, 12, FColor::Red, false, -1.0f, 0, 1.0f);
        DrawDebugString(GetWorld(), Location + FVector(0, 0, 100), AIStateToString(CurrentState), nullptr, FColor::White, 0.0f, true);
    }
}
  1. 로그 출력
void AMyAIController::LogAIState()
{
    UE_LOG(LogAI, Verbose, TEXT("AI State: %s, Target: %s"), *AIStateToString(CurrentState), *GetNameSafe(CurrentTarget));
}

확장 가능한 AI 아키텍처 설계

 모듈화된 AI 시스템을 구현하여 확장성을 높일 수 있습니다.

UCLASS(Abstract)
class UAIModule : public UObject
{
    GENERATED_BODY()
 
public:
    virtual void UpdateModule(float DeltaTime) PURE_VIRTUAL(UAIModule::UpdateModule, );
};
 
UCLASS()
class AModularAIController : public AAIController
{
    GENERATED_BODY()
 
private:
    UPROPERTY()
    TArray<UAIModule*> AIModules;
 
public:
    void AddAIModule(TSubclassOf<UAIModule> ModuleClass);
    virtual void Tick(float DeltaTime) override;
};
 
void AModularAIController::AddAIModule(TSubclassOf<UAIModule> ModuleClass)
{
    UAIModule* NewModule = NewObject<UAIModule>(this, ModuleClass);
    if (NewModule)
    {
        AIModules.Add(NewModule);
    }
}
 
void AModularAIController::Tick(float DeltaTime)
{
    Super::Tick(DeltaTime);
 
    for (UAIModule* Module : AIModules)
    {
        Module->UpdateModule(DeltaTime);
    }
}

 이러한 모듈화된 접근 방식을 통해 새로운 AI 기능을 쉽게 추가하고 기존 기능을 재사용할 수 있습니다.

 언리얼 엔진의 AI 시스템은 강력하고 유연한 도구를 제공하여 복잡한 AI 행동을 구현할 수 있게 해줍니다. AAIController 클래스를 기반으로 커스텀 AI 컨트롤러를 만들고, 다양한 AI 행동을 구현할 수 있습니다.

 기본적인 이동, 회전, 목표 추적 등의 기능부터 시작하여, 시야와 청각을 포함한 인식 시스템, 상태 관리, 그리고 플레이어와의 상호작용까지 구현할 수 있습니다. 이를 통해 게임 세계에서 자연스럽게 반응하고 행동하는 AI 캐릭터를 만들 수 있습니다.

 성능 최적화를 위해서는 틱 최적화와 LOD 시스템 구현 등의 전략을 사용할 수 있습니다. 또한, 시각적 디버깅과 로깅을 통해 AI의 행동을 쉽게 분석하고 디버그할 수 있습니다.

 마지막으로, 모듈화된 AI 아키텍처를 설계함으로써 확장성과 재사용성을 높일 수 있습니다. 이를 통해 복잡한 AI 시스템을 효율적으로 개발하고 관리할 수 있습니다.

 AI 시스템 개발 시 항상 성능과 게임플레이 경험 사이의 균형을 유지하는 것이 중요합니다. 지속적인 테스트와 최적화를 통해 효율적이고 흥미로운 AI 시스템을 구축할 수 있습니다.