안동민 개발노트

본문 시작

간단한 AI 의사결정 모델 구현

센서·블랙보드·비헤이비어 트리·내비게이션을 연결해 순찰·감지·추적·공격·탐색 순환을 구현합니다.

지금까지 우리는 비헤이비어 트리, EQS, 내비게이션, 그리고 센서 시스템이라는 AI 구축의 핵심 도구들을 개별적으로 살펴보았습니다.

이제 이 모든 요소들을 결합하여, 플레이어를 감지하고, 추적하고, 공격하거나, 플레이어를 놓치면 순찰하는 간단한 AI 의사결정 모델을 구현해보겠습니다.

이는 AI가 인지 → 판단 → 행동의 순환을 통해 작동하는 기본적인 패턴을 보여줄 것입니다.


AI 의사결정 모델의 목표

우리가 구현할 AI는 다음과 같은 의사결정 과정을 따릅니다.

초기 상태: 순찰(Patrol) 또는 대기(Idle)

플레이어 감지: AI Perception을 통해 플레이어를 시각으로 확인하거나 청각으로 마지막 소음 위치를 얻습니다.

추적/전투: 현재 보이는 플레이어를 추적하고, 공격 시점에도 대상·거리·권한 조건을 확인합니다. 소리만 들었을 때는 발생 위치를 조사합니다.

플레이어 놓침: 시야를 잃으면 마지막으로 확인한 위치를 탐색합니다. 청각 성공 이벤트도 조사할 위치를 제공하며 현재 시야 확보로 취급하지 않습니다.

탐색 실패: 마지막 감지 위치에서도 플레이어를 찾지 못하면 다시 순찰 상태로 돌아갑니다.


AI 의사결정 시스템 구현을 위한 준비물

  • 플레이어 캐릭터: BP_PlayerCharacter (AI Perception Stimuli Source가 설정되어 있어야 함)
  • AI 폰/캐릭터: BP_EnemyCharacter (NavMesh 위에 배치될 것임)
  • AI 컨트롤러: AIC_Enemy (AI Perception Component 및 Run Behavior Tree 설정)
  • 블랙보드: BB_EnemyAI (필요한 키: TargetActor (Object), TargetLocation (Vector), IsPlayerDetected (Boolean), PatrolLocation (Vector))
  • 비헤이비어 트리: BT_EnemyAI (AI의 행동 로직)
  • 내비메시 볼륨: 레벨에 배치되어 AI가 이동할 수 있는 영역 설정

AI 의사결정 구현 단계별 가이드

이전 절에서 다룬 내용을 바탕으로 각 요소를 이미 설정했다고 가정하고, 이제 이들을 비헤이비어 트리에 결합하는 데 중점을 둡니다.

블랙보드 키 추가

BB_EnemyAI 블랙보드에 다음 키들을 추가합니다.

  • TargetActor (Object, Base Class: Actor)
  • TargetLocation (Vector)
  • IsPlayerDetected (Boolean) - 이 실습에서는 현재 시각으로 확인했는지 여부
  • PatrolLocation (Vector) - AI가 순찰할 목표 위치

AI 컨트롤러 (AIC_Enemy) 설정

AIC_Enemy의 AI Perception 컴포넌트에서 On Target Perception Updated 이벤트를 처리하여 블랙보드를 업데이트합니다.

이 예제는 플레이어 1명을 대상으로 하며 IsPlayerDetected를 현재 시각 확인 여부로 정의합니다. 이벤트의 Actor를 플레이어로 확인하고 Stimulus의 감각 종류를 분리합니다. 초기화된 Blackboard Component의 Set Value as Object/Vector/Bool과 Clear Value를 사용합니다.

  • Sight 성공: TargetActor와 확인한 TargetLocation을 저장하고 IsPlayerDetected = true로 설정합니다.
  • Sight 상실: 현재 타깃에 해당할 때 IsPlayerDetected = false, TargetActor는 비우고 기존 TargetLocation은 유지합니다.
  • Hearing 성공: 현재 시각 타깃이 없을 때만 Stimulus Location을 TargetLocation에 저장합니다. IsPlayerDetected를 true로 바꾸거나 보이지 않는 Actor의 현재 위치를 읽지 않습니다.
  • Hearing 실패/만료: 현재 시야나 다른 감각의 유효한 증거를 지우지 않습니다. 여기서는 기억 위치를 탐색 완료 때 지우며, 별도의 기억 제한 시간을 두려면 타이머 정책을 추가합니다.

비헤이비어 트리 (BT_EnemyAI) 구현

이제 BT_EnemyAI를 열고 다음 구조로 AI의 의사결정 모델을 만듭니다. 코드 모양의 표기는 노드 배치 설명이며, Decorator와 Service는 해당 Sequence/Task에 부착합니다. Vector의 Is Set을 사용해 유효한 원점 (0,0,0)과 미설정 상태를 구분합니다. Blackboard 데코레이터의 Notify Observer는 On Result Change로 두고, Both가 자기 분기와 오른쪽의 낮은 우선순위 분기를 중단하도록 설정합니다.

Root
  |
  Selector (AI의 메인 의사결정, 우선순위: 전투/탐색 > 순찰)
    |
    +--- Sequence (플레이어 추적/공격)
    |      |
    |      +--- Decorator: Blackboard (IsPlayerDetected == True, Observer Aborts: Both)
    |      +--- Decorator: Blackboard (TargetActor Is Set, Observer Aborts: Self)
    |      |      (플레이어가 감지되었을 때만 이 브랜치를 실행)
    |      |
    |      +--- Task: Move To (Blackboard Key: TargetActor, Acceptable Radius: 150)
    |      |      (플레이어에게 접근)
    |      |
    |      +--- Task: Attack Player (새로운 커스텀 태스크 블루프린트)
    |      |      (공격 로직 - 애니메이션 재생, 데미지 적용 등)
    |      |
    |      +--- Service: Update Target Location (플레이어 위치를 지속적으로 TargetLocation에 업데이트)
    |             (부모인 Sequence 노드에 붙여, 해당 시퀀스가 실행되는 동안 주기적으로 실행되도록)
    |             (IsPlayerDetected이고 TargetActor가 유효할 때만 현재 위치 저장)
    |
    +--- Sequence (플레이어 탐색)
    |      |
    |      +--- Decorators: IsPlayerDetected == False AND TargetLocation Is Set
    |      |      (각 Blackboard 조건을 Sequence에 부착, Observer Aborts: Both)
    |      |      (플레이어를 놓쳤지만 마지막 위치 정보가 있을 때)
    |      |
    |      +--- Task: Move To (Blackboard Key: TargetLocation, Acceptable Radius: 50)
    |      |      (Force Success 데코레이터: 이동 실패도 아래 대기·위치 해제로 이어감)
    |      |      (마지막 감지 위치로 이동)
    |      |
    |      +--- Task: Wait (3.0초 대기)
    |      |      (해당 위치에서 잠시 탐색)
    |      |
    |      +--- Task: Clear Last Known Location (새로운 커스텀 태스크 블루프린트)
    |             (탐색을 마쳤으므로 TargetLocation 키를 Clear Blackboard Value로 지움)
    |
    +--- Sequence (순찰/대기)
           |
           +--- Task: Find Patrol Location (새로운 커스텀 태스크 블루프린트)
           |      (현재 위치 주변의 랜덤한 이동 가능한 지점을 찾아 PatrolLocation에 저장)
           |      (NavSystemLib의 Get Random Reachable Point in Radius 노드 활용)
           |
           +--- Task: Move To (Blackboard Key: PatrolLocation, Acceptable Radius: 100)
           |      (순찰 지점으로 이동)
           |
           +--- Task: Wait (5.0초 대기)
                  (잠시 대기 후 다음 순찰 지점 탐색)
새로운 태스크 블루프린트 생성

위 비헤이비어 트리 구조에 필요한 커스텀 태스크를 생성합니다.

BTTask_AttackPlayer (공격 태스크)
  • 콘텐츠 브라우저에서 마우스 우클릭 > 블루프린트 클래스(Blueprint Class) > All Classes에서 BTTask_BlueprintBase를 검색하여 선택합니다.
  • 그래프 탭: Receive Execute AI 이벤트를 오버라이드합니다.
  • 대상 유효성·거리·공격 가능 조건을 검사하고 실패하면 Finish Execute(false)로 끝냅니다. 통과하면 몽타주를 재생하고 공격 시점에 유효한 피해 대상을 다시 확인합니다.
  • 완료 콜백에서 Finish Execute(true), 재생 실패에서는 false로 끝냅니다. Receive Abort AI에서는 공격 판정·콜백을 정리하고 Finish Abort로 중단을 마칩니다. 중단된 태스크의 늦은 콜백이 새 공격을 완료시키지 않도록 활성 상태도 검사합니다.
BTTask_ClearLastKnownLocation (마지막 위치 지우기 태스크)
  • BTTask_BlueprintBase를 상속받아 생성합니다.
  • 그래프 탭: Receive Execute AI 이벤트를 오버라이드합니다.
  • Get Blackboard Component 노드를 호출하여 블랙보드 참조를 얻습니다.
  • 블랙보드 참조에서 Clear Value 노드를 호출하고 Key Name 핀에 TargetLocation (키 이름)을 연결합니다.
  • Finish Execute 노드를 호출하고 Success를 True로 설정합니다.
BTTask_FindPatrolLocation (순찰 위치 찾기 태스크)
  • BTTask_BlueprintBase를 상속받아 생성합니다.
  • 그래프 탭: Receive Execute AI 이벤트를 오버라이드합니다.
  • Receive Execute AI의 Controlled Pawn을 유효성 검사하고 현재 위치를 얻습니다.
  • Get Random Reachable Point in Radius 노드를 호출합니다.
    • Origin은 AI의 현재 위치.
    • Radius는 순찰 반경 (예: 1000.0).
  • 반환 Boolean이 true일 때만 찾은 위치를 Blackboard Component의 Set Value as Vector로 PatrolLocation에 저장하고 Finish Execute(true)를 호출합니다.
  • 실패하면 키를 비우고 Finish Execute(false)로 끝냅니다. 연속 실패 시 재시도 간 대기나 유효한 시작 위치 복구를 두어 매 프레임 같은 실패를 반복하지 않게 합니다.

BTService_UpdateTargetLocation

플레이어를 추적하는 동안 플레이어의 위치를 지속적으로 업데이트하는 서비스입니다.

서비스 블루프린트 생성
  • 콘텐츠 브라우저에서 마우스 우클릭 > 블루프린트 클래스(Blueprint Class) > All Classes에서 BTService_BlueprintBase를 검색하여 선택합니다.
  • 이름을 BTS_UpdateTargetLocation으로 지정합니다.
그래프 탭
  • Receive Tick AI이벤트를 오버라이드합니다.
  • Behavior Tree에 붙인 Service의 Details에서 Interval을 0.5초 정도로 설정하고 Random Deviation도 확인합니다. 정확한 0.5초 주기 보장이나 측정값은 아닙니다.
  • Controlled Pawn 핀에서 Cast To Character (또는 Cast To BP_EnemyCharacter)를 연결합니다.
  • Owner Controller 핀에서 Get Blackboard Component 노드를 호출합니다.
  • Blackboard Component에서 Get Value as Object 노드를 호출하고 Key Name 핀에 TargetActor를 연결합니다.
  • Get Value as Object의 Return Value에서 Cast To BP_PlayerCharacter를 연결합니다.
  • 캐스팅 성공 및 IsPlayerDetected = true일 때만 Get Actor Location을 호출합니다. 시야를 잃은 대상의 위치를 이 서비스가 계속 갱신하지 않게 합니다.
  • 이 위치를 Blackboard Component의 Set Value as Vector 노드를 통해 TargetLocation 블랙보드 키에 저장합니다.
  • 컴파일 및 저장합니다.

레벨 배치 및 테스트

레벨에 Nav Mesh Bounds Volume이 AI 이동 영역을 포함하도록 배치하고 P 키를 눌러 내비메시를 확인합니다.

BP_EnemyCharacter를 레벨에 배치합니다.

게임을 플레이하고 AI의 행동을 관찰합니다.

  • AI가 순찰하다가 플레이어를 발견하면 추적하고 공격합니다.
  • 플레이어가 시야에서 사라지거나 소리만 들리면, AI는 마지막으로 플레이어를 본 위치로 이동하여 잠시 탐색합니다.
  • 탐색에도 실패하면 다시 순찰을 시작합니다.
디버깅
  • PIE의 Gameplay Debugger(기본 apostrophe 키)에서 AI 경로·감각 정보를 확인하고, 트리 에디터의 Blackboard 패널에서 실제 키 값을 봅니다.
  • 비헤이비어 트리 에디터에서 디버그(Debug) 드롭다운 메뉴를 통해 실행 중인 AI를 선택하고, 트리의 활성화된 노드를 실시간으로 확인합니다.

추가적인 개선 사항

이 모델은 기본적인 의사결정의 예시이며, 실제 게임에서는 더욱 복잡한 로직이 추가됩니다.

  • 다양한 공격 패턴: 근접 공격, 원거리 공격, 스킬 사용 등을 Attack Player 태스크 내부에서 또는 별도의 태스크로 분리하여 구현.
  • 회피 및 엄폐: EQS를 활용하여 엄폐 위치를 찾고 이동하는 로직 추가.
  • 피해 반응: 데미지를 입었을 때 특정 애니메이션을 재생하거나, 후퇴하는 행동 추가.
  • 그룹 AI: 여러 AI가 협동하거나 경쟁하는 로직 구현.
  • 감지 수준: 플레이어의 행동(예: 웅크리기, 빠르게 달리기)에 따라 감지될 확률이나 거리가 달라지도록 설정.