생성자
...
#include "AIController.h"
#include "NavigationSystem.h"
...
AUS_Minion::AUS_Minion()
{
...
AutoPossessAI = EAutoPossessAI::PlacedInWorldOrSpawned;
AIControllerClass = AAIController::StaticClass();
PawnSense = CreateDefaultSubobject<UPawnSensingComponent>(TEXT("PawnSense"));
PawnSense->SensingInterval = .8f;
PawnSense->SetPeripheralVisionAngle(45.f);
PawnSense->SightRadius = 1500.f;
PawnSense->HearingThreshold = 400.f;
PawnSense->LOSHearingThreshold = 800.f;
Collision = CreateDefaultSubobject<USphereComponent>(TEXT("Collision"));
Collision->SetSphereRadius(100);
Collision->SetupAttachment(RootComponent);
...
}
- EAutoPossessAI: 게임 시스템이 레벨에 있는 AI 캐릭터를 제어할지 여부를 정의한다. 즉, AutoPossessAI 프로퍼티를 PlacedInWorldOrSpawned 값으로 설정하여 AI 캐릭터가 런타임에 스폰될 때와 게임 시작 시 AI 캐릭터가 이미 레벨에 있을 때의 상황을 모두 완전히 제어한다.
- AAIController: AI 시스템에 사용될 컨트롤러를 정의한다. 여기서는 기본 AAIController 클래스를 사용하지만, 추가 기능을 가진 자체 컨트롤러를 정의할 수 있다.
PawnSense 변수에 초기화된 UPawnSensingComponent는 IA 감지에 대한 설정으로, 언리얼 문서를 참고하면 좋다. 다만 SensingInterval에 대해 설명하자면, 감각 인식을 할 때 첫 번째 인식과 두 번째 인식 사이의 시간 간격을 조정할 수 있다.
미니언 초기화
void AUS_Minion::BeginPlay()
{
Super::BeginPlay();
SetNextPatrolLocation();
}
void AUS_Minion::PostInitializeComponents()
{
Super::PostInitializeComponents();
if (GetLocalRole() != ROLE_Authority)
return;
OnActorBeginOverlap.AddDynamic(this, &AUS_Minion::OnBeginOverlap);
GetPawnSense()->OnSeePawn.AddDynamic(this, &AUS_Minion::OnPawnDetected);
}
- PostInitializeComponents(): 액터의 컴포넌트가 초기화된 후에 호출된다.
해당 코드를 보면 액터 오버랩에 반응하기 위해 2개의 델리게이트를 사용하고 있다. 하나는 플레이어 캐릭터에 도달했는지를 확인하고, 다른 하나는 폰 인식을 처리해 플레이어 캐릭터를 볼 수 있는지를 확인한다.
델리게이트 함수 처리
void AUS_Minion::OnPawnDetected(APawn* Pawn)
{
if (!Pawn->IsA<AUS_Character>())
return;
GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Red, TEXT("Character detected!"));
if (GetCharacterMovement()->MaxWalkSpeed != ChaseSpeed)
{
Chase(Pawn);
}
}
- IsA: 해당 클래스가 맞는지 확인하는 역할을 한다. 즉, 미니언이 폰을 감지할 때마다 그 폰이 캐릭터인지 즉시 확인하고, 감지된 폰이 캐릭터이면 미니언이 그 캐릭터를 추격할 것이다.
void AUS_Minion::OnBeginOverlap(AActor* OverlappedActor, AActor* OtherActor)
{
if (!OtherActor->IsA<AUS_Character>())
return;
GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Yellow, TEXT("Character captured!"));
}
언리얼을 하면서 가장 이해가 안 되는 델리게이트 바인딩 함수다. 이전 글에서 BasePickUp은 지금보다 더 많은 매개변수를 요구했는데, 그 이유는 컴포넌트인지 액터인지의 차이점 때문에 발생한다. 이 부분은 아직까지도 완벽하게 이해가 안 되어 일단 그냥 받아들이기로 한다.
void AUS_Minion::SetNextPatrolLocation()
{
if (GetLocalRole() != ROLE_Authority)
return;
GetCharacterMovement()->MaxWalkSpeed = PatrolSpeed;
const auto LocationFound = UNavigationSystemV1::K2_GetRandomReachablePointInRadius(this, GetActorLocation(), PatrolLocation, PatrolRadius);
if (LocationFound)
{
UAIBlueprintHelperLibrary::SimpleMoveToLocation(GetController(), PatrolLocation);
}
}
- UNavigationSystemV1::K2_GetRandomReachablePointInRadius(): GetActorLocation()를 중심으로 PatrolRadius 반경 내에서 이동 가능한 랜덤 위치를 탐색한다. 이후 탐색된 위치가 PatrolLocation에 저장된다.
- UAIBlueprintHelperLibrary::SimpleMoveToLocation(): AI 컨트롤러에게 목표 위치(PatrolLocation)로 이동하라고 명령을 한다.
void AUS_Minion::Chase(APawn* Pawn)
{
if (GetLocalRole() != ROLE_Authority)
return;
GetCharacterMovement()->MaxWalkSpeed = ChaseSpeed;
UAIBlueprintHelperLibrary::SimpleMoveToActor(GetController(), Pawn);
DrawDebugSphere(GetWorld(), Pawn->GetActorLocation(), 25.f, 12, FColor::Red, true, 10.f, 0, 2.f);
}
UAIBlueprintHelperLibrary::SimpleMoveToLocatio: 플레이어를 간단히 이동시키는 함수가 있다. 이와 반대되는 함수로는 Behavior Tree가 존재한다. Behavior Tree는 AI의 복잡한 행동을 정의하고 제어하는 데 사용된다.
Tick() 이벤트 구현
void AUS_Minion::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
if (GetLocalRole() != ROLE_Authority)
return;
if (GetMovementComponent()->GetMaxSpeed() == ChaseSpeed)
return;
if ((GetActorLocation() - PatrolLocation).Size() < 500.f)
{
SetNextPatrolLocation();
}
}
AI와 목표 지점 사이의 거리를 지속적으로 확인해야 하므로, 이 코드를 작성하기에 가장 좋은 위치는 Tick() 이벤트이다. Tick() 이벤트는 매 프레임마다 호출되므로, 거리 계산을 실시간으로 수행할 수 있다.
- GetMovementComponent()->GetMaxSpeed() == ChaseSpeed: 현재 상태가 추적 중인 속도인 경우, 순찰 로직 실행을 막는 역할을 한다. 이를 통해 AI가 플레이어를 추적하는 동안에는 불필요한 순찰 동작을 하지 않도록 할 수 있다.
- (GetActorLocation() - PatrolLocation).Size() < 500.f: 현재 위치와 목적지의 거리가 500 유닛 이하인지 확인한다. 이를 통해 AI가 목표에 가까워졌는지를 판단할 수 있다.
요약
- Minion 클래스는 BeginPlay()를 통해 게임이 시작하자마자 SetNextPatrolLocation()을 호출하여 순찰을 시작한다.
- UNavigationSystemV1::K2_GetRandomReachablePointInRadius()를 사용하여 랜덤으로 목적지를 정한다.
- 플레이어가 감지되면 OnPawnDetected 함수가 호출되어 플레이어인지 검사한 후 Chase()를 통해 플레이어를 추적한다.
- Chase() 함수 내에서는 SimpleMoveToActor를 사용하여 단순히 추적만 수행한다.
- 오버랩이 발생하면 OnBeginOverlap()을 통해 플레이어와 캐릭터인지 확인한 후 관련 로직을 실행한다.
- 만약 범위를 벗어나면 다시 순찰을 시작한다.
'Unreal > 언리얼 엔진 5로 개발하는 멀티플레이어 게임(Book)' 카테고리의 다른 글
[Unreal] 소음, 감지 코드 분석 (0) | 2025.04.01 |
---|---|
[Unreal] US_MinionSpawner.cpp 코드 분석 (0) | 2025.03.30 |
[Unreal] 3D Object Edit Pivot 설정 (0) | 2025.03.29 |
[Unreal] US_Character Class 코드 분석(RPC 상호작용) (0) | 2025.03.29 |
[Unreal] US_Interactable.h 코드 분석(인터페이스) (0) | 2025.03.29 |