내일배움캠프 언리얼트랙 40일차 - 보안요원 AI Controller

2026. 6. 18. 11:55·내일배움캠프

MoveToLocation

	const FVector StartLocation = ControllPawn->GetActorLocation();
	const FVector TargetLocation = StartLocation + FVector(500.0f, 0.0f, 0.0f);
	
	MoveToLocation(TargetLocation);
	UE_LOG(LogTemp, Warning, TEXT("Security AI: MoveToLocation Test Started"));

 

 

MoveToActor

	APawn* PlayerPawn = UGameplayStatics::GetPlayerPawn(this,0);
	if (!PlayerPawn)
	{
		UE_LOG(LogTemp, Warning, TEXT("Security AI: PlayerPawn is null"));
		return;
	}
	MoveToActor(PlayerPawn);

 

 

AI Perception, AI Sense Config

헤더 추가
#include "Perception/AIPerceptionComponent.h"
#include "Perception/AISenseConfig_Sight.h"
#include "Perception/AIPerceptionTypes.h"
객체 생성

SecurityPerceptionComponent = CreateDefaultSubobject<UAIPerceptionComponent>(TEXT("SecurityPerceptionComponent"));
SightConfig = CreateDefaultSubobject<UAISenseConfig_Sight>(TEXT("SightConfig"));

UAIPerceptionComponent는 AI의 감지 기능을 담당하는 컴포넌트

UAISenseConfiig_Sight는 시야 범위, 시야각, 감지 유지 거리 등의 설정을 담당하는 객체

SightConfig->SightRadius = 1500.0f;
SightConfig->LoseSightRadius = 1800.0f;
SightConfig->PeripheralVisionAngleDegrees = 80.0f;
SightConfig->SetMaxAge(3.0f);

SightRadius = 처음 대상을 감지하는 거리

LoseSightRadius = 이미 감지한 대상을 놓치는 거리

PeripheralVisionAngleDegrees = 시야각

SetMaxAge = 감지 정보를 기억하는 시간

SightConfig->DetectionByAffiliation.bDetectEnemies = true;
SightConfig->DetectionByAffiliation.bDetectFriendlies = true;
SightConfig->DetectionByAffiliation.bDetectNeutrals = true;

DetectionByAffiliation = 적/아군/중립 감지 여부

SecurityPerceptionComponent->ConfigureSense(*SightConfig);
SecurityPerceptionComponent->SetDominantSense(SightConfig->GetSenseImplementation());
SetPerceptionComponent(*SecurityPerceptionComponent);

ConfigureSense()를 통해 시야 설정을 PerceptionComponent에 적용 가능

SecurityPerceptionComponent->OnTargetPerceptionUpdated.AddDynamic(
	this,
	&AFTSecurityAIController::OnTargetPerceptionUpdated
);

OnTargetPerceptionUpdated = 감지 상태가 바뀔 때 호출됨

즉, 플레이어를 발견하거나 놓쳤을 때 실행됨.

 

Broadcast

헤더 추가
#include "GameFramework/GameplayMessageSubsystem.h"
#include "ProjectFT/Message/FTGameplayTags.h"
#include "ProjectFT/Struct/FTNPCReportPayloadStruct.h"
FFTNPCReportPayloadStruct Payload;
Payload.TargetActor = PlayerPawn;
Payload.ReportLocation = PlayerPawn->GetActorLocation();
Payload.ReportAmount = 100.0f;
Payload.ReportProgress = 1.0f;

UGameplayMessageSubsystem::Get(World).BroadcastMessage(
	TAG_FT_Event_SecurityCalled,
	Payload
);

BroadCastMessage() = 특정 GameplayTag 채널로 메시지를 보내는 코드

위 코드에선 콘솔 명령어 ft.Security.TestCall을 실행하면 Event.Security.Called 메시지를 발행한다.

 

FTNPCReportPayloadStruct에 다음 정보를 담아 전달한다.

TargetActor = 신고된 대상

ReportLocation = 신고 위치

ReportAmount = 신고 수치

ReportProgress = 신고 진행도

 

Listener

SecurityCalledListenerHandle = MessageSubsystem.RegisterListener(
	TAG_FT_Event_SecurityCalled, 
	this, 
	&ThisClass::OnSecurityCalled
);

RegisterListener() = 특정 메시지를 듣기 위해 등록하는 코드

위 코드에서는 TAG_FT_Event_SecurityCalled 메시지가 발행되면 OnSecurityCalled() 함수가 자동으로 호출됨.

void AFTSecurityAIController::OnSecurityCalled(
	FGameplayTag Channel,
	const FFTNPCReportPayloadStruct& Payload
)
{
	// 신고 메시지 처리
}
if (SecurityCalledListenerHandle.IsValid())
{
	UGameplayMessageSubsystem::Get(this).UnregisterListener(SecurityCalledListenerHandle);
}

UnregisterListener() = AIController가 사라질 때 등록된 리스너를 해제하는 코드.

이렇게 해제하지 않으면 이미 사라진 객체가 메시지를 받으려고 해서 문제가 생길 수 있음.

 

정리

BroadcastMessage()
→ “신고 발생!” 메시지를 보냄

RegisterListener()
→ “신고 발생!” 메시지를 기다림

OnSecurityCalled()
→ 메시지를 받았을 때 실제 행동 결정

AI Perception
→ 이동 중 플레이어를 직접 감지하면 추격 시작

 

 

 


트러블 슈팅

1. 보안요원의 감지 범위 밖에서 호출됐는데도 플레이어를 따라옴

- 원래 구현하고자 했던 방향 : 감지 범위 밖에서 호출되면 호출 된 위치로만 이동. 감지 범위 내에서 호출됐을 때 플레이어를 감지하면 플레이어를 추격.

신호 받음
↓
TargetActor 유효성 확인
↓
보안요원 기준으로 TargetActor까지 거리 계산
↓
LoseSightRadius 안쪽이면
    → SetTargetActor()
    → StartChase()
↓
LoseSightRadius 바깥이면
    → SetTargetActor()는 해두되
    → MoveToLocation(Payload.ReportLocation)
    → 추격은 하지 않음
↓
이동 중 Perception이 TargetActor를 감지하면
    → OnTargetPerceptionUpdated()
    → StartChase()

 

- 원인 : 현재 MoveToActor(TargetActor)는 범위 밖이어도 Actor 자체를 계속 추적하기 때문에, 플레이어가 움직이면 그대로 따라감.

void AFTSecurityAIController::OnSecurityCalled(FGameplayTag Channel, const FFTNPCReportPayloadStruct& Payload)
{
	// 보안 호출 메시지에 추적 대상이 없으면 처리 불가
	if (!Payload.TargetActor)
	{
		UE_LOG(LogTemp, Warning, TEXT("Security AI: Security called but TargetActor is null"));
		return;
	}

	// 메시지로 전달받은 플레이어를 현재 추적 대상으로 설정
	SetTargetActor(Payload.TargetActor);

	// 추격 시작
	StartChase();

	UE_LOG(LogTemp, Log, TEXT("Security AI: Security called, chasing %s"), *Payload.TargetActor->GetName());
}

 

< 해결 >

TargetActor가 LoseSightRadius 안에 있음
→ StartChase()
→ MoveToActor(TargetActor)

TargetActor가 LoseSightRadius 밖에 있음
→ MoveToLocation(Payload.ReportLocation)
→ 호출 당시 위치로 이동
→ 실제 추격은 Perception으로 다시 감지됐을 때 시작
void AFTSecurityAIController::OnSecurityCalled(FGameplayTag Channel, const FFTNPCReportPayloadStruct& Payload)
{
	// 신고 메시지에 추적 대상이 없으면 처리할 수 없으므로 종료
	if (!Payload.TargetActor)
	{
		UE_LOG(LogTemp, Warning, TEXT("Security AI: TargetActor is null"));
		return;
	}

	// AI가 조종 중인 Pawn 또는 시야 설정이 없으면
	// 거리 판단이 불가능하므로 종료
	const APawn* ControlledPawn = GetPawn();
	if (!ControlledPawn || !SightConfig)
	{
		UE_LOG(LogTemp, Warning, TEXT("Security AI: ControlledPawn or SightConfig is null"));
		return;
	}

	// 신고된 플레이어를 현재 추적 대상으로 저장
	// (즉시 추격 여부는 아래 거리 검사 후 결정)
	SetTargetActor(Payload.TargetActor);

	// 보안요원과 플레이어 사이의 현재 거리 계산
	const float DistanceToTarget = FVector::Dist(
		ControlledPawn->GetActorLocation(),
		Payload.TargetActor->GetActorLocation()
	);

	// 플레이어가 감지 유지 범위(LoseSightRadius) 안에 있다면
	// 즉시 추격 시작
	if (DistanceToTarget <= SightConfig->LoseSightRadius)
	{
		StartChase();

		UE_LOG(LogTemp, Log,
			TEXT("Security AI: Target in range, chasing %s"),
			*Payload.TargetActor->GetName());

		return;
	}

	// 플레이어가 감지 범위 밖에 있다면
	// 신고가 들어온 위치를 조사하러 이동
	const FVector InvestigateLocation =
		Payload.ReportLocation.IsNearlyZero()
		? Payload.TargetActor->GetActorLocation() // 신고 위치가 없으면 현재 위치 사용
		: Payload.ReportLocation;                 // 신고 위치가 있으면 해당 위치 사용

	// 조사 위치로 이동
	const EPathFollowingRequestResult::Type MoveResult =
		MoveToLocation(InvestigateLocation, 150.0f);

	UE_LOG(LogTemp, Log,
		TEXT("Security AI: Investigating location %s, result %d"),
		*InvestigateLocation.ToString(),
		static_cast<int32>(MoveResult));
}
TargetActor 없음 → 종료
Pawn / SightConfig 없음 → 종료
TargetActor 저장
거리 계산
LoseSightRadius 안 → StartChase()
LoseSightRadius 밖 → ReportLocation으로 이동

 

 

2. 플레이어와 접촉 후 추격 멈춤

플레이어를 추격하다가 접촉하면 보안요원의 감지 범위 내에 있어도 플레이어 추격을 멈춤. 감지 범위를 벗어나려 하면 그제서야 다시 추적함.

- 원래 구현하고자 했던 방향 : 추격하다가 플레이어가 공격 범위 안에 들어오면 공격(공격구현은 아직). 감지 범위 내에 있으면 계속 추적.

- 원인 : MoveToActor()가 한 번 “도착 완료”로 끝나버림, MoveToActor(TargetActor)AI Perception의 OnTargetPerceptionUpdated는 계속 보이는 중에는 매 프레임 호출되고 있지 않음.

현재 흐름
Security.Called
→ MoveToActor(TargetActor)
→ 보안요원이 플레이어에게 접근
→ 플레이어와 캡슐 충돌/겹침 또는 근접
→ AI가 “목표에 도착했다”고 판단
→ 이동 요청 종료

즉, 플레이어가 게속 시야안에 있으면 새 이벤트가 없음. -> MoveToActor()를 다시 호출할 일이 없어서 추격을 멈춤.

그런데! 플레이어가 보안요원의 시야각을 벗어나려고 하면 Perception 상태가 변함 -> OnTargetPerceptionUpdated()가 호출되어 그제서야 MoveToActor()를 실행함.

애가 일머리가 읎어...

문제점 정리
부딪힘
→ MoveToActor 완료 처리
→ Perception은 계속 보이는 상태라 새 이벤트 없음
→ 이동 명령이 다시 안 들어감
→ 멈춤
→ 시야를 벗어나려 하면 Perception 이벤트 발생
→ 다시 MoveToActor 호출
→ 다시 따라옴

 

GPT에게 물어보니 Tick에서 매 프레임 호출하는 것으로 구현을 하면 가능하다는데.. 말고도 StateTree로도 이 문제를 해결할 수 있다고 한다. 내일 StateTree로 이 문제를 해결해 볼 예정이다.


 

오늘의 영상

https://youtu.be/vvaRqGFa4yE?si=CaE1INz-pxfuAWAr

 

'내일배움캠프' 카테고리의 다른 글

내일배움캠프 언리얼트랙 42일차 - 손님NPC AI Controller, State Tree  (0) 2026.06.22
내일배움캠프 언리얼트랙 41일차 - 보안요원 State Tree  (0) 2026.06.19
내일배움캠프 언리얼트랙 38일차 - 심화반 2강  (0) 2026.06.16
내일배움캠프 언리얼트랙 35일차 - TA 1강  (0) 2026.06.11
내일배움캠프 언리얼트랙 34일차 - 과제04  (0) 2026.06.10
'내일배움캠프' 카테고리의 다른 글
  • 내일배움캠프 언리얼트랙 42일차 - 손님NPC AI Controller, State Tree
  • 내일배움캠프 언리얼트랙 41일차 - 보안요원 State Tree
  • 내일배움캠프 언리얼트랙 38일차 - 심화반 2강
  • 내일배움캠프 언리얼트랙 35일차 - TA 1강
thinklikethink
thinklikethink
생각처럼 개발 공부 블로그입니다.
  • thinklikethink
    생각처럼
    thinklikethink
  • 전체
    오늘
    어제
    • 분류 전체보기 (53) N
      • 사전캠프 (13)
      • 내일배움캠프 (40) N
  • 블로그 메뉴

    • 홈
    • 내일배움캠프
    • 사전캠프
  • 링크

  • 공지사항

  • 인기 글

  • 태그

  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.6
thinklikethink
내일배움캠프 언리얼트랙 40일차 - 보안요원 AI Controller
상단으로

티스토리툴바