[DAY53] Shooter Project(3) : Tactical AI Behavior Tree

베리투스·2025년 10월 24일
0

Shooter Project

목록 보기
4/10

지난 시간까지 만든 AI는 플레이어를 보면 쫓아오고, 놓치면 수색하는 단순한 '반응형 AI'였다. 이번에는 여기서 한 단계 더 나아가, 서비스(Service)를 활용해 AI가 스스로 상황을 분석하고 더 똑똑한 판단을 내리는 '전술적 AI'를 구현하는 과정을 정리했다. 핵심은 실시간으로 변하는 데이터를 블랙보드에 지속적으로 업데이트하고, 이를 바탕으로 행동 트리가 더 유연한 의사결정을 내리게 하는 것이다. 🧠


📌 목표

  • 서비스(Service)의 역할과 사용법 정리
  • 블랙보드에 '상황 분석 데이터' (플레이어와의 거리, 자신의 체력 등)를 추가하고 관리하는 방법 학습
  • 분석된 데이터를 기반으로, 거리에 따라 다른 공격 패턴을 사용하도록 행동 트리 구성
  • 자신의 체력 상태에 따라 공격과 후퇴를 결정하는 '생존 본능' 로직 구현

📖 이론

  1. 서비스 (Service)란?

    • AI의 '상황 분석가' 🕵️‍♂️
    • 행동 트리의 특정 가지(Branch)에 붙어서, 그 가지가 활성화되어 있는 동안 주기적으로 특정 로직을 실행한다.
    • 주된 역할은 AI 자신이나 주변 환경의 상태를 계산/분석하여 그 결과를 블랙보드에 업데이트하는 것이다.
    • 중요: 서비스는 직접적인 행동(이동, 공격 등)을 수행하지 않는다. 오직 정보 수집과 블랙보드 업데이트에만 집중해야 역할이 명확해지고 코드가 깔끔해진다.
  2. 서비스와 데코레이터의 협업 관계

    • 서비스 (정보 생산자): "현재 플레이어와의 거리는 750.0f 이다." 라는 정보를 계산해서 블랙보드의 DistanceToTarget 키에 쓴다(Write).
    • 데코레이터 (정보 소비자): 행동을 결정하기 전에 블랙보드의 DistanceToTarget 키를 읽고(Read), "값이 500보다 큰가?" 를 검사하여 조건이 맞으면 자식 노드를 실행시킨다.

    이 둘의 협업 덕분에 AI는 실시간으로 변화하는 전장 상황에 맞춰 유연하게 행동을 바꿀 수 있었다.


💻 코드

1. 블랙보드 키 추가

BB_Monster에 새로운 키를 추가했다.

  • DistanceToTarget (Type: Float): 플레이어와의 거리를 저장할 키.
  • MyHealth (Type: Float): AI 자신의 현재 체력을 저장할 키.

2. C++ 서비스 생성

UBTService_UpdateTacticalInfo 라는 이름으로 새로운 C++ BT Service 클래스를 생성했다.

BTS_UpdateTacticalInfo.h

#pragma once

#include "CoreMinimal.h"
#include "BehaviorTree/BTService.h"
#include "BTS_UpdateTacticalInfo.generated.h"

UCLASS()
class SPARTA_TPROJECT_02_API UBTS_UpdateTacticalInfo : public UBTService
{
    GENERATED_BODY()

public:
    UBTS_UpdateTacticalInfo();

protected:
    // 서비스가 주기적으로 호출할 함수
    virtual void TickNode(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, float DeltaSeconds) override;

private:
    // 에디터에서 설정할 블랙보드 키 선택자
    UPROPERTY(EditAnywhere, Category = "Blackboard")
    FBlackboardKeySelector DistanceToTargetKey;

    UPROPERTY(EditAnywhere, Category = "Blackboard")
    FBlackboardKeySelector MyHealthKey;
};

BTS_UpdateTacticalInfo.cpp

#include "BTS_UpdateTacticalInfo.h"
#include "AIController.h"
#include "BehaviorTree/BlackboardComponent.h"
#include "GameFramework/Character.h"
#include "AIMonsterBase.h" // AAIMonsterBase 헤더 포함

UBTS_UpdateTacticalInfo::UBTS_UpdateTacticalInfo()
{
    NodeName = TEXT("Update Tactical Info");
    Interval = 0.5f; // 0.5초마다 실행
    RandomDeviation = 0.1f; // 실행 주기에 약간의 무작위성 부여
}

void UBTS_UpdateTacticalInfo::TickNode(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, float DeltaSeconds)
{
    Super::TickNode(OwnerComp, NodeMemory, DeltaSeconds);

    AAIController* AIController = OwnerComp.GetAIOwner();
    UBlackboardComponent* BlackboardComp = OwnerComp.GetBlackboardComponent();
    if (AIController == nullptr || BlackboardComp == nullptr)
    {
        return;
    }

    // 1. AI 자신(Pawn)과 타겟 액터 가져오기
    APawn* ControllingPawn = AIController->GetPawn();
    if (ControllingPawn == nullptr) return;

    AActor* TargetActor = Cast<AActor>(BlackboardComp->GetValueAsObject(TEXT("TargetActor")));

    // 2. 타겟이 있을 경우, 거리 계산하여 블랙보드에 업데이트
    if (TargetActor)
    {
        float Distance = FVector::Dist(ControllingPawn->GetActorLocation(), TargetActor->GetActorLocation());
        BlackboardComp->SetValueAsFloat(DistanceToTargetKey.SelectedKeyName, Distance);
    }
    else
    {
        // 타겟이 없으면 키 값 초기화
        BlackboardComp->ClearValue(DistanceToTargetKey.SelectedKeyName);
    }

    // 3. 자신의 체력 정보를 블랙보드에 업데이트
    AAIMonsterBase* MyMonster = Cast<AAIMonsterBase>(ControllingPawn);
    if (MyMonster)
    {
        BlackboardComp->SetValueAsFloat(MyHealthKey.SelectedKeyName, MyMonster->CurrentHealth);
    }
}

3. 행동 트리(BT) 구성

새로 만든 서비스를 활용하여 행동 트리를 재구성했다. 전투 관련 로직을 담당하는 Selector 노드에 BTS_UpdateTacticalInfo 서비스를 추가했다.

[Selector] (여기에 'BTS_UpdateTacticalInfo' 서비스 추가)
├── [Sequence] (후퇴)
│   ├── [Decorator: Blackboard] MyHealth Is Less Than 30.0f
│   └── [Task: Find Safe Location & Move] // (안전한 곳으로 이동하는 태스크, 추후 구현)
│
├── [Sequence] (원거리 저격)
│   ├── [Decorator: Blackboard] DistanceToTarget Is Greater Than 1000.0f
│   └── [Task: Snipe Attack]
│
├── [Sequence] (일반 사격)
│   ├── [Decorator: Blackboard] DistanceToTarget Is Less Than 1000.0f
│   └── [Task: Normal Attack]
│
└── [추격/수색/순찰]
  • 작동 방식: 전투에 돌입하면(TargetActor가 설정되면), BTS_UpdateTacticalInfo 서비스가 활성화되어 0.5초마다 거리와 체력 정보를 블랙보드에 갱신한다. Selector는 이 최신 정보를 바탕으로 '후퇴', '저격', '일반 사격' 중 가장 우선순위가 높고 조건에 맞는 행동을 선택하게 된다.

⚠️ 실수

  • 데코레이터의 Observer Aborts 설정을 잊은 문제: 순찰하던 AI가 플레이어를 발견하고 추격을 시작했는데, 플레이어가 공격 범위에 들어와도 바로 공격하지 않고 추격을 마저 하려는 이상한 행동을 보였다. 확인해보니 공격 조건(거리가 가까운지)을 검사하는 데코레이터의 Observer Aborts 속성이 None으로 되어 있었다. 이 설정을 Lower Priority로 변경해주니, 추격(낮은 우선순위) 중에 공격(높은 우선순위) 조건이 만족되면 즉시 추격을 중단하고 공격을 시작했다. AI의 반응성을 결정하는 매우 중요한 설정이라는 것을 깨달았다.
  • 몬스터끼리 시야를 가리는 문제: 좁은 복도에 몬스터 여러 마리를 배치했더니, 맨 앞의 몬스터만 플레이어를 인지하고 뒤따라오는 몬스터들은 앞선 몬스터에 가려 플레이어를 보지 못하는 현상이 발생했다. 결국 앞 몬스터가 죽어야만 그 다음 몬스터가 반응했다. AIPerception은 기본적으로 다른 Pawn에 의해 시야가 가로막힌다. 이를 해결하기 위해 청각(Hearing) 감각을 추가로 설정했다. 플레이어가 총을 쏘거나 뛸 때 발생하는 소리(MakeNoise 함수 사용)를 AI가 감지하게 하여, 시야가 확보되지 않아도 소리가 난 위치로 일단 이동하도록 로직을 개선했다.

✅ 핵심 요약

개념설명비고
서비스 (Service)BT의 특정 가지가 활성화된 동안, 주기적으로 로직을 실행하여 블랙보드 데이터를 갱신하는 노드.AI의 '상황 분석가'. 행동을 직접 하진 않음.
전술적 AI서비스가 분석한 데이터를 바탕으로, 데코레이터가 더 복잡한 조건(거리, 체력 등)을 판단하여 행동을 결정하는 AI.단순 반응형 AI보다 한 단계 높은 지능.
Observer Aborts데코레이터의 핵심 속성. Lower Priority로 설정 시, 하위 우선순위 작업 중에 상위 우선순위 조건이 만족되면 즉시 현재 작업을 중단하고 전환.AI의 반응성에 결정적인 영향을 미침.
다중 감각 활용시각(Sight)만으로는 한계가 있을 때, 청각(Hearing) 등 다른 감각을 추가하여 AI의 인지 능력을 보완할 수 있음.좁은 공간이나 엄폐물이 많은 환경에서 특히 유용.
profile
Shin Ji Yong // Unreal Engine 5 공부중입니다~

0개의 댓글