본문으로 건너뛰기
게임 수학 · 게임 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 미완료 상태