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 |