본문으로 건너뛰기
게임 수학 · 게임 AI 기초집LESSON 19

추적 AI

난이도초급
예상 시간55분
선수지식18강의 순찰 AI

19강. 추적 AI

1. 이번 강의에서 해결할 문제

적이 플레이어를 한 번 발견하면 영원히 추적하거나, 벽 뒤로 숨는 순간 즉시 원래 위치로 돌아갑니다. 시야를 잃은 시간과 마지막 확인 위치를 관리합니다.

2. 학습 목표

3. 핵심 개념

추적은 매 프레임 플레이어를 찾는 기능이 아닙니다. Service가 제한된 주기로 거리·시야를 갱신하고 Blackboard Observer가 Branch를 바꿉니다.

플레이어가 잠깐 기둥 뒤로 숨었을 때 즉시 포기하면 부자연스럽습니다. 마지막으로 본 위치와 시간을 저장해 몇 초 동안 그 위치를 확인한 뒤 순찰로 돌아갑니다.

4. 단계별 실습

Blackboard 작업

LastKnownLocation Vector와 LastSeenTime Float Key를 추가합니다.

C++ · 파일 경로: Source/TopDownSurvival/AI/MathAIController.h · 공개 갱신 API

MathAIController.h · 추가
public:
void RefreshCombatContext();

protected:
UPROPERTY(EditDefaultsOnly, Category = "AI|Senses",
meta = (ClampMin = "0.0", Units = "s"))
float LoseTargetDelay = 2.0f;

C++ · 파일 경로: Source/TopDownSurvival/AI/Services/BTService_UpdateCombatContext.h

BTService_UpdateCombatContext.h
#pragma once

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

UCLASS()
class TOPDOWNSURVIVAL_API UBTService_UpdateCombatContext : public UBTService
{
GENERATED_BODY()

public:
UBTService_UpdateCombatContext();

protected:
virtual void TickNode(
UBehaviorTreeComponent& OwnerComp,
uint8* NodeMemory,
float DeltaSeconds) override;
};

C++ · 파일 경로: Source/TopDownSurvival/AI/Services/BTService_UpdateCombatContext.cpp

BTService_UpdateCombatContext.cpp
#include "AI/Services/BTService_UpdateCombatContext.h"

#include "AI/MathAIController.h"

UBTService_UpdateCombatContext::UBTService_UpdateCombatContext()
{
NodeName = TEXT("Update Combat Context");
Interval = 0.25f;
RandomDeviation = 0.05f;
}

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

if (AMathAIController* Controller =
Cast<AMathAIController>(OwnerComp.GetAIOwner()))
{
Controller->RefreshCombatContext();
}
}

C++ · MathAIController의 핵심 갱신

MathAIController.cpp · RefreshCombatContext
#include "Kismet/GameplayStatics.h"

void AMathAIController::RefreshCombatContext()
{
APawn* Player = UGameplayStatics::GetPlayerPawn(this, 0);
APawn* Enemy = GetPawn();
UBlackboardComponent* Blackboard = GetBlackboardComponent();
if (!Enemy || !Blackboard) return;

const bool bVisible = IsValid(Player) && HasClearSight(Player);
const float Now = GetWorld()->GetTimeSeconds();

if (bVisible)
{
Blackboard->SetValueAsObject(TEXT("TargetActor"), Player);
Blackboard->SetValueAsVector(
TEXT("LastKnownLocation"), Player->GetActorLocation());
Blackboard->SetValueAsFloat(TEXT("LastSeenTime"), Now);
}

const float LastSeen = Blackboard->GetValueAsFloat(TEXT("LastSeenTime"));
const bool bWithinMemory = Now - LastSeen <= LoseTargetDelay;
if (!bVisible && !bWithinMemory)
{
Blackboard->ClearValue(TEXT("TargetActor"));
}

const float Distance = IsValid(Player)
? FVector::Dist2D(Enemy->GetActorLocation(), Player->GetActorLocation())
: TNumericLimits<float>::Max();
Blackboard->SetValueAsFloat(TEXT("DistanceToTarget"), Distance);
Blackboard->SetValueAsBool(TEXT("HasLineOfSight"), bVisible);

if (bVisible)
{
SetAIState(DetermineState(Distance, true));
}
else
{
SetAIState(bWithinMemory
? EEnemyAIState::Chase
: EEnemyAIState::Patrol);
}
}

Service를 BT Root 아래 Selector에 연결합니다. Chase Branch는 TargetActor가 설정됐을 때 MoveTo TargetActor 또는 LastKnownLocation을 실행합니다.

5. 코드와 수학·AI가 동작하는 이유

Service Interval과 RandomDeviation은 여러 적의 Trace가 같은 프레임에 몰리는 것을 줄입니다. 보일 때만 마지막 위치와 시간을 갱신하고 일정 유예 뒤 목표를 지워 자연스러운 탐색 시간을 만듭니다.

6. 자주 하는 실수와 해결법

7. 직접 실습

8. 이해 점검 질문 3개

9. 핵심 요약

MINI QUIZ

추적 AI 미니 퀴즈

선택 즉시 정답과 해설을 확인할 수 있습니다. 결과는 이 브라우저에만 저장됩니다.

0 / 2
  1. 문제 1“추적 AI”에서 다음 단계로 넘어가기 전에 확인할 핵심은 무엇인가요?
  2. 문제 2“추적 AI” 실습 중 ‘Service Interval 0으로 사실상 매 Tick 실행’ 상황을 발견했습니다. 본문과 일치하는 설명은 무엇인가요?
LESSON STATUS

학습을 마쳤나요?

직접 실습과 점검 질문까지 확인한 뒤 완료로 표시하세요.

19강. 추적 AI 미완료 상태