언리얼 엔진 5

[UE5] NavLink - 단절된 NavMesh 구간을 연결하는 기술

언린이 2026. 3. 23. 23:35
반응형

1. 이 기능이 필요한 이유

AI 에이전트의 이동은 NavMesh(Navigation Mesh) 위에서 이루어집니다. NavMesh는 레벨의 걸을 수 있는 표면을 삼각형 폴리곤으로 구워낸 데이터이므로, 물리적으로 연결되지 않은 두 표면 사이에는 자동으로 경로가 생성되지 않습니다.

예를 들어 다음과 같은 상황을 생각해봅니다.

  • 적 AI가 2층 발코니에서 1층 아래로 뛰어 내려와야 하는 상황
  • 플랫포머 게임에서 AI가 낮은 플랫폼에서 높은 플랫폼으로 점프해야 하는 상황
  • 사다리나 로프를 타고 이동해야 하는 구간
  • 워프 포탈을 통과해 다른 위치로 순간이동해야 하는 상황

이 모든 경우에서 AI의 PathFollowingComponent는 NavMesh 경계에 막혀 더 이상 경로를 계산하지 못합니다. NavMesh가 물리적으로 분리된 두 영역을 하나의 연결된 그래프로 인식하지 못하기 때문입니다.

NavLink는 바로 이 문제를 해결합니다. NavMesh 상에 "가상의 엣지(Off-Mesh Link)"를 추가하여, 물리적으로 단절된 두 지점을 경로 탐색 그래프 안에서 연결합니다. NavLink가 설정되면 AI의 경로 탐색 알고리즘(Recast A)은 해당 구간을 정상적인 이동 경로의 일부로 포함시켜 계산합니다. 단, *NavLink는 "이 경로로 갈 수 있다"는 정보만 NavMesh에 전달할 뿐이며, 실제로 AI를 어떻게 이동시킬지(점프 애니메이션, 물리 적용 등)는 개발자가 직접 구현해야 합니다.

2. NavLink의 두 가지 종류

언리얼 엔진 5에서 NavLink는 크게 두 가지 방식으로 제공됩니다. 두 방식 모두 ANavLinkProxy 액터를 통해 레벨에 배치되지만, 내부 구현 방식과 활용 목적이 다릅니다.

2-1. Simple Link (단순 링크)

ANavLinkProxyPointLinks 배열에 등록되는 정적(Static) 링크입니다. 시작 지점(Left)과 끝 지점(Right)을 지정하면, NavMesh 빌드 시 해당 두 지점을 잇는 Off-Mesh 연결이 NavMesh 데이터에 구워집니다.

Simple Link의 특징은 다음과 같습니다.

  • NavMesh가 빌드될 때 연결 정보가 확정됩니다.
  • 런타임에 동적으로 비활성화하거나 비용을 변경하기 어렵습니다.
  • PointLinkAreaClass(예: NavArea_Jump, 커스텀 클래스)를 지정하여 이동 비용과 필터 플래그를 제어할 수 있습니다.
  • 하나의 ANavLinkProxy 인스턴스에 여러 개의 Simple Link를 등록할 수 있습니다.
  • 이동 방향을 단방향(One Way) 또는 양방향(Both Ways)으로 설정할 수 있습니다.

2-2. Smart Link (스마트 링크)

ANavLinkProxy가 내장하는 UNavLinkCustomComponent(멤버 이름: SmartLinkComp)를 통해 제공되는 동적(Dynamic) 링크입니다. Smart Link의 핵심 기능은 AI 에이전트가 링크 구간에 진입하는 순간 콜백 이벤트를 발생시킨다는 점입니다.

Smart Link의 특징은 다음과 같습니다.

  • SmartLinkIsRelevant 프로퍼티로 런타임에 링크를 활성화/비활성화할 수 있습니다.
  • 활성화 상태와 비활성화 상태에 각각 다른 AreaClass를 지정할 수 있습니다.
  • AI 에이전트가 링크의 시작 지점에 도달하면 OnSmartLinkReached 이벤트(델리게이트)가 호출됩니다.
  • 이 이벤트 안에서 점프 물리, 워프 텔레포트, 애니메이션 재생 등 실제 이동 처리를 구현합니다.
  • 이동 처리가 완료된 후 ResumePathFollowing()을 호출하여 경로 추적을 재개합니다.
  • NavMesh 리빌드 없이도 링크의 활성 상태와 AreaClass를 변경할 수 있습니다.
  • 하나의 ANavLinkProxy 인스턴스에는 Smart Link가 최대 하나만 존재합니다.

두 종류의 링크는 하나의 ANavLinkProxy 인스턴스에서 동시에 사용할 수 있습니다.

3. 핵심 클래스와 관계 구조

NavLink 시스템을 구성하는 주요 클래스들의 역할을 정리하면 다음과 같습니다.

클래스 역할
ANavLinkProxy NavLink를 레벨에 배치하는 액터 컨테이너. Simple Link와 Smart Link를 모두 포함.
FNavigationLink Simple Link 하나를 표현하는 구조체. Left/Right 지점, 방향, AreaClass 포함.
UNavLinkCustomComponent Smart Link의 실제 구현체. 에이전트 진입 감지 및 콜백 처리.
UNavArea NavMesh 영역 또는 링크에 이동 비용과 쿼리 필터 플래그를 부여하는 클래스.
NavArea_Jump 점프 행동에 사용되는 엔진 기본 제공 NavArea 클래스.
UPathFollowingComponent AI 에이전트가 경로를 따라 이동하도록 지시하는 컴포넌트. Smart Link 도달 시 일시 정지.

4. Simple Link 설정 및 C++ 구현

Simple Link는 에디터에서 NavLinkProxy 액터를 배치한 뒤 디테일 패널에서 PointLinks 배열에 항목을 추가하는 방식으로 설정합니다. C++에서는 런타임 또는 생성자에서 프로그래밍 방식으로 설정할 수도 있습니다.

4-1. 에디터 설정 절차

  1. 콘텐츠 브라우저에서 NavLinkProxy를 레벨에 배치합니다. (Place Actors 패널 → 검색어 "NavLink")
  2. 액터를 선택한 뒤 디테일 패널에서 Point Links 배열에 항목을 추가합니다.
  3. 각 항목의 Left 위즈젯(초록 구)을 링크의 시작 지점, Right 위즈젯(빨간 구)을 끝 지점으로 이동시킵니다.
  4. DirectionLeft to Right(단방향) 또는 Both Ways(양방향)으로 설정합니다.
  5. Area ClassNavArea_Jump 또는 커스텀 NavArea 클래스로 지정합니다.
  6. NavMesh를 리빌드합니다. (P 키 또는 에디터 상단 네비게이션 리빌드 버튼)

4-2. C++로 NavLinkProxy 서브클래스 생성

에디터 배치 없이 코드로 Simple Link를 설정하는 예시입니다. 먼저 ANavLinkProxy를 상속받은 커스텀 액터 클래스를 선언합니다.

// AJumpNavLinkProxy.h
#pragma once

#include "CoreMinimal.h"
#include "NavLinkProxy.h"
#include "AJumpNavLinkProxy.generated.h"

UCLASS()
class MYGAME_API AJumpNavLinkProxy : public ANavLinkProxy
{
    GENERATED_BODY()

public:
    AJumpNavLinkProxy();
};

생성자에서 PointLinks 배열에 FNavigationLink를 추가하여 Simple Link를 초기화합니다.

// AJumpNavLinkProxy.cpp
#include "AJumpNavLinkProxy.h"
#include "NavAreas/NavArea_Jump.h"
#include "Navigation/NavLinkDefinition.h"

AJumpNavLinkProxy::AJumpNavLinkProxy()
{
    // 기존 배열을 비우고 새로 설정
    PointLinks.Empty();

    FNavigationLink JumpLink;
    JumpLink.Left  = FVector(0.f, -200.f, 0.f);   // 출발 지점 (로컬 좌표)
    JumpLink.Right = FVector(0.f,  200.f, -150.f); // 도착 지점 (로컬 좌표, 아래로 150 낙하)
    JumpLink.Direction = ENavLinkDirection::LeftToRight; // 단방향: Left → Right 만 허용
    JumpLink.SetAreaClass(UNavArea_Jump::StaticClass()); // 점프 플래그를 가진 영역으로 지정

    PointLinks.Add(JumpLink);
}

위 코드처럼 FNavigationLink::DirectionENavLinkDirection::LeftToRight를 설정하면, 경로 탐색기는 Left에서 Right 방향으로만 이 링크를 비용 계산에 포함시킵니다. 반대 방향은 불가능한 경로로 간주하므로, AI가 반대로 이동해야 할 때는 NavMesh 위의 우회로를 탐색하게 됩니다.

5. Smart Link 구현 — 점프 · 낙하 · 워프 패턴

Smart Link는 AI가 링크 시작 지점에 도달했을 때 OnSmartLinkReached 델리게이트를 통해 콜백을 받으므로, 이 콜백 안에서 실제 이동 처리를 구현합니다. 아래에서는 가장 자주 사용되는 세 가지 패턴을 설명합니다.

5-1. 낙하(Drop-Off) 패턴

AI가 엣지에서 아래로 낙하하는 상황입니다. 별도의 제어 없이 중력에 맡겨 자연스럽게 떨어지게 하거나, LaunchCharacter로 약간의 초기 속도를 부여할 수 있습니다.

먼저 ANavLinkProxy를 상속받아 Smart Link 콜백을 등록하는 낙하 전용 클래스를 선언합니다.

// ADropOffNavLink.h
#pragma once

#include "CoreMinimal.h"
#include "NavLinkProxy.h"
#include "ADropOffNavLink.generated.h"

UCLASS()
class MYGAME_API ADropOffNavLink : public ANavLinkProxy
{
    GENERATED_BODY()

public:
    ADropOffNavLink();

protected:
    // Smart Link 도달 시 호출되는 콜백
    UFUNCTION()
    void OnSmartLinkReached(AActor* MovingActor, const FVector& DestinationPoint);
};

생성자에서 Smart Link를 활성화하고, 델리게이트에 콜백을 바인딩합니다.

// ADropOffNavLink.cpp
#include "ADropOffNavLink.h"
#include "GameFramework/Character.h"
#include "Navigation/PathFollowingComponent.h"
#include "AIController.h"

ADropOffNavLink::ADropOffNavLink()
{
    // Smart Link 활성화
    SmartLinkIsRelevant = true;

    // 에이전트가 Smart Link 시작 지점에 도달하면 콜백 호출
    SmartLinkComp->OnSmartLinkReached.AddDynamic(
        this, &ADropOffNavLink::OnSmartLinkReached);
}

void ADropOffNavLink::OnSmartLinkReached(
    AActor* MovingActor, const FVector& DestinationPoint)
{
    ACharacter* Character = Cast<ACharacter>(MovingActor);
    if (!Character) return;

    // 중력으로 자연 낙하시키기 위해 PathFollowing을 즉시 재개
    // PathFollowingComponent가 DestinationPoint를 향해 이동하면서
    // CharacterMovementComponent의 물리가 낙하를 처리합니다.
    AAIController* AIController = Cast<AAIController>(Character->GetController());
    if (AIController)
    {
        // LaunchCharacter로 약간의 수평 속도 부여 (선택)
        Character->LaunchCharacter(
            FVector(0.f, 0.f, -100.f), // 아래로 초기 속도
            false, false);

        // 경로 추적 재개 — 이 시점부터 AI는 DestinationPoint를 향해 다시 이동
        AIController->GetPathFollowingComponent()->FinishUsingCustomLink(
            SmartLinkComp);
    }
}

위 코드에서 FinishUsingCustomLink(SmartLinkComp)를 호출하는 것이 핵심입니다. 이 함수를 호출하지 않으면 PathFollowingComponent가 Smart Link 처리 중 상태로 영구히 멈춰 있어 AI가 정지합니다.

5-2. 점프(Jump) 패턴

AI가 포물선 궤적을 그리며 플랫폼에서 플랫폼으로 점프하는 상황입니다. LaunchCharacter에 적절한 속도 벡터를 계산하여 전달하고, 착지 시점을 감지한 뒤 경로 추적을 재개합니다.

착지 감지를 위해 CharacterMovementComponentOnLanded 델리게이트 또는 Tick 기반 폴링을 활용할 수 있습니다. 아래는 OnLanded 방식의 구현 예시입니다.

// AJumpNavLink.h
#pragma once

#include "CoreMinimal.h"
#include "NavLinkProxy.h"
#include "AJumpNavLink.generated.h"

UCLASS()
class MYGAME_API AJumpNavLink : public ANavLinkProxy
{
    GENERATED_BODY()

public:
    AJumpNavLink();

    // 점프 최고점 높이 (언리얼 단위, cm)
    UPROPERTY(EditAnywhere, Category = "JumpLink")
    float JumpApexHeight = 300.f;

protected:
    UFUNCTION()
    void OnSmartLinkReached(AActor* MovingActor, const FVector& DestinationPoint);

private:
    // 착지 감지 후 경로 재개를 위한 약참조
    TWeakObjectPtr<AAIController> PendingController;

    UFUNCTION()
    void OnCharacterLanded(const FHitResult& Hit);
};
// AJumpNavLink.cpp
#include "AJumpNavLink.h"
#include "GameFramework/Character.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "AIController.h"
#include "Navigation/PathFollowingComponent.h"

AJumpNavLink::AJumpNavLink()
{
    SmartLinkIsRelevant = true;
    SmartLinkComp->OnSmartLinkReached.AddDynamic(
        this, &AJumpNavLink::OnSmartLinkReached);
}

void AJumpNavLink::OnSmartLinkReached(
    AActor* MovingActor, const FVector& DestinationPoint)
{
    ACharacter* Character = Cast<ACharacter>(MovingActor);
    if (!Character) return;

    AAIController* AIController = Cast<AAIController>(Character->GetController());
    if (!AIController) return;

    // 출발 지점과 도착 지점 사이의 수평 거리 계산
    const FVector StartLocation  = Character->GetActorLocation();
    const FVector Delta          = DestinationPoint - StartLocation;
    const float   HorizontalDist = Delta.Size2D();

    // 포물선 운동 공식으로 초기 속도 계산
    // 정점 높이 h에서 수직 속도: Vz = sqrt(2 * |g| * h)
    const float Gravity = FMath::Abs(Character->GetCharacterMovement()->GetGravityZ());
    const float Vz      = FMath::Sqrt(2.f * Gravity * JumpApexHeight);

    // 정점 도달 시간: t_up = Vz / |g|
    // 하강 시간: t_down = sqrt(2 * (h + 높이차) / |g|)
    // 전체 비행 시간 T 동안 수평 이동 = HorizontalDist
    const float HeightDiff = Delta.Z;
    const float TotalTime  = (Vz + FMath::Sqrt(Vz * Vz + 2.f * Gravity * (-HeightDiff))) / Gravity;
    const float Vh         = (TotalTime > SMALL_NUMBER) ? HorizontalDist / TotalTime : 0.f;

    // 수평 방향 단위 벡터에 수평 속도 부여
    const FVector HorizontalDir = FVector(Delta.X, Delta.Y, 0.f).GetSafeNormal();
    const FVector LaunchVelocity = HorizontalDir * Vh + FVector(0.f, 0.f, Vz);

    // 착지 이벤트에 경로 재개 콜백 바인딩
    PendingController = AIController;
    Character->LandedDelegate.AddDynamic(this, &AJumpNavLink::OnCharacterLanded);

    // 점프 실행 (현재 속도를 덮어씌움)
    Character->LaunchCharacter(LaunchVelocity, true, true);
}

void AJumpNavLink::OnCharacterLanded(const FHitResult& Hit)
{
    // 착지 이벤트 바인딩 해제 (중복 호출 방지)
    ACharacter* Character = Cast<ACharacter>(Hit.GetActor());
    if (Character)
    {
        Character->LandedDelegate.RemoveDynamic(this, &AJumpNavLink::OnCharacterLanded);
    }

    // 경로 추적 재개
    if (PendingController.IsValid())
    {
        PendingController->GetPathFollowingComponent()->FinishUsingCustomLink(SmartLinkComp);
        PendingController.Reset();
    }
}

위 코드처럼 포물선 공식을 이용해 LaunchCharacter에 전달할 초기 속도를 계산하면, 출발 지점과 도착 지점의 높이 차이가 달라져도 자동으로 올바른 궤적이 계산됩니다. LandedDelegate에 콜백을 바인딩하여 착지 시점을 정확히 감지한 뒤 경로를 재개하는 것이 핵심 패턴입니다.

5-3. 워프(Warp / Teleport) 패턴

워프 포탈, 마법 문, 순간이동 장치처럼 AI가 즉시 다른 위치로 이동해야 하는 상황입니다. 복잡한 물리 계산 없이 SetActorLocation으로 위치를 변경하고 경로를 재개합니다.

// AWarpNavLink.h
#pragma once

#include "CoreMinimal.h"
#include "NavLinkProxy.h"
#include "AWarpNavLink.generated.h"

UCLASS()
class MYGAME_API AWarpNavLink : public ANavLinkProxy
{
    GENERATED_BODY()

public:
    AWarpNavLink();

    // 워프 도착 지점 (월드 좌표)
    UPROPERTY(EditAnywhere, Category = "WarpLink")
    FVector WarpDestination = FVector::ZeroVector;

    // 워프 후 AI가 바라볼 방향 (선택, 영벡터면 기존 방향 유지)
    UPROPERTY(EditAnywhere, Category = "WarpLink")
    FRotator WarpFacingRotation = FRotator::ZeroRotator;

protected:
    UFUNCTION()
    void OnSmartLinkReached(AActor* MovingActor, const FVector& DestinationPoint);
};
// AWarpNavLink.cpp
#include "AWarpNavLink.h"
#include "GameFramework/Character.h"
#include "AIController.h"
#include "Navigation/PathFollowingComponent.h"

AWarpNavLink::AWarpNavLink()
{
    SmartLinkIsRelevant = true;
    SmartLinkComp->OnSmartLinkReached.AddDynamic(
        this, &AWarpNavLink::OnSmartLinkReached);
}

void AWarpNavLink::OnSmartLinkReached(
    AActor* MovingActor, const FVector& DestinationPoint)
{
    ACharacter* Character = Cast<ACharacter>(MovingActor);
    if (!Character) return;

    // 순간이동: 도착 지점으로 위치 변경
    // bSweep=false로 충돌 스윕 없이 즉시 이동
    Character->SetActorLocation(WarpDestination, false, nullptr, ETeleportType::TeleportPhysics);

    // 바라볼 방향이 지정된 경우 회전도 변경
    if (!WarpFacingRotation.IsZero())
    {
        Character->SetActorRotation(WarpFacingRotation);
    }

    // 속도 초기화 (워프 후 이전 관성이 남아 있지 않도록)
    Character->GetCharacterMovement()->Velocity = FVector::ZeroVector;

    // 경로 추적 즉시 재개
    AAIController* AIController = Cast<AAIController>(Character->GetController());
    if (AIController)
    {
        AIController->GetPathFollowingComponent()->FinishUsingCustomLink(SmartLinkComp);
    }
}

위 코드처럼 워프 패턴에서는 ETeleportType::TeleportPhysics를 반드시 지정해야 합니다. ResetPhysics를 전달하면 물리 상태가 완전히 초기화되어 의도치 않은 동작이 생길 수 있고, None을 전달하면 이전 위치에서 새 위치로 스윕 처리가 발생합니다. TeleportPhysics는 위치만 바꾸되 물리 상태를 적절히 보존합니다.

6. 런타임 Smart Link 동적 제어

Smart Link의 강점은 게임 중에 링크를 켜고 끄거나, 통과 비용을 바꿀 수 있다는 점입니다. 예를 들어 문이 잠겨 있을 때는 링크를 비활성화하고, 문이 열리면 활성화하는 방식으로 NavMesh 리빌드 없이 AI의 경로 선택에 영향을 줄 수 있습니다.

아래는 문(Door)에 부착된 NavLink를 문 잠금 상태에 따라 토글하는 예시입니다.

// ADoorActor.h (관련 부분만 발췌)
#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "ADoorActor.generated.h"

class ANavLinkProxy;

UCLASS()
class MYGAME_API ADoorActor : public AActor
{
    GENERATED_BODY()

public:
    // 디테일 패널에서 문과 연결할 NavLinkProxy를 지정
    UPROPERTY(EditInstanceOnly, Category = "Navigation")
    TObjectPtr<ANavLinkProxy> DoorNavLink;

    // 문 잠금/해제 처리
    void SetDoorLocked(bool bLocked);
};
// ADoorActor.cpp
#include "ADoorActor.h"
#include "NavLinkProxy.h"

void ADoorActor::SetDoorLocked(bool bLocked)
{
    if (!DoorNavLink) return;

    // Smart Link의 활성 여부를 문 잠금 상태의 반대로 설정
    // 잠긴 상태(bLocked=true) → 링크 비활성화 → AI는 이 경로를 선택하지 않음
    // 열린 상태(bLocked=false) → 링크 활성화 → AI가 문을 통과하는 경로를 선택 가능
    DoorNavLink->SetSmartLinkEnabled(!bLocked);

    // 문 애니메이션, 사운드 등 추가 처리
    // ...
}

위 코드에서 SetSmartLinkEnabled 함수는 ANavLinkProxy에 정의된 함수로, 내부적으로 UNavLinkCustomComponent::SetEnabled()를 호출합니다. 이 함수는 NavMesh 빌드 없이 링크의 활성 상태만 바꾸므로 런타임 오버헤드가 매우 낮습니다.

7. UE5.5 자동 NavLink 생성

UE5.5부터 NavMesh 설정에서 자동 NavLink 생성 기능이 실험적으로 제공됩니다. RecastNavMesh-Default 액터의 디테일 패널 → Generation 섹션 → Generate Nav Links 체크박스를 활성화하면, 엔진이 NavMesh 엣지를 분석하여 낙하 가능한 구간에 NavLink를 자동으로 생성합니다.

자동 생성은 Nav Link Jump Down Config 설정을 기반으로 합니다. 주요 설정 항목은 다음과 같습니다.

설정 항목 설명
Jump Length 점프의 수평 최대 거리. 값이 클수록 생성 비용이 높아집니다.
Jump Max Depth 출발 지점 아래로 탐색할 최대 낙하 깊이.
Jump Height 출발 지점 기준 점프 정점 높이.
Area Class 자동 생성된 링크에 적용할 NavArea 클래스.
Link Proxy Class 자동 생성된 링크의 Smart Link 콜백을 처리할 NavLinkProxy 서브클래스.

자동 생성 기능은 Link Proxy Class에 커스텀 NavLinkProxy 블루프린트 또는 C++ 클래스를 지정할 수 있어, 자동 생성된 링크에도 앞서 설명한 점프 · 낙하 패턴을 그대로 적용할 수 있습니다. 다만 이 기능은 UE5.5 기준으로 실험 단계(Experimental)이므로, 프로덕션 환경에서는 충분한 검증 후 사용을 권장합니다.

8. 유니티 엔진과의 비교

유니티의 NavMesh 시스템에도 NavLink와 유사한 기능이 존재합니다. NavMesh.AddLink API와 패키지 매니저에서 제공하는 NavMeshLink 컴포넌트가 그것입니다.

두 시스템의 차이점을 정리하면 다음과 같습니다.

항목 언리얼 엔진 5 (NavLink) 유니티 (NavMeshLink)
배치 방식 ANavLinkProxy 액터를 레벨에 배치 NavMeshLink 컴포넌트를 게임오브젝트에 부착
콜백 시스템 Smart Link의 OnSmartLinkReached 델리게이트 NavMeshAgentupdatePosition / updateRotationfalse로 설정 후 직접 제어
동적 제어 SetSmartLinkEnabled() 한 번의 호출로 즉시 토글 NavMeshLink.activated 프로퍼티 변경
비용 설정 UNavArea 서브클래스로 비용과 쿼리 필터를 세밀하게 제어 NavMeshLink.costModifier로 상대 비용 조절
자동 생성 UE5.5부터 실험적 자동 생성 지원 자동 생성 미지원 (수동 배치만 가능)

언리얼 엔진 NavLink의 가장 큰 강점은 Smart Link의 델리게이트 콜백 시스템입니다. 유니티는 NavMeshAgent가 링크 구간에 진입했을 때 에이전트를 직접 제어하려면 updatePositionupdateRotation을 수동으로 관리해야 하고, 이동 완료 후 다시 복원하는 코드를 개발자가 직접 작성해야 합니다. 반면 언리얼 엔진은 Smart Link 진입 시 PathFollowingComponent가 자동으로 일시 정지하고 콜백을 발생시키며, FinishUsingCustomLink 한 번의 호출로 재개됩니다. 덕분에 링크 이동 처리 로직이 NavLink 클래스 안에 깔끔하게 캡슐화됩니다.

또한 언리얼 엔진의 UNavArea 기반 비용 시스템은 에이전트별 쿼리 필터(FNavigationQueryFilter)와 결합하여, 에이전트 종류에 따라 특정 링크를 사용하거나 무시하는 세밀한 제어를 제공합니다. 예를 들어 점프 능력이 없는 중형 AI는 NavArea_Jump 플래그가 있는 링크를 필터링하여 우회로를 탐색하도록 설정할 수 있습니다.

9. 주의사항

9-1. FinishUsingCustomLink 미호출 시 AI 정지

Smart Link 콜백을 구현할 때 가장 흔한 실수는 FinishUsingCustomLink를 호출하지 않는 것입니다. 이 함수를 호출하지 않으면 PathFollowingComponent는 Smart Link 처리 중 상태로 영구히 대기하며, AI는 그 자리에서 멈춥니다. 점프나 워프 처리 중 예외가 발생하더라도 반드시 이 함수가 호출되도록 방어 코드를 작성하셔야 합니다.

9-2. 양방향(Both Ways) 설정 시 메시 구조 확인

양방향 Simple Link를 설정할 때는 양쪽 방향 모두 AI가 실제로 이동 가능한 구조인지 확인해야 합니다. 낙하 링크를 양방향으로 설정하면 AI가 아래에서 위로 "걸어 올라가려는" 경로를 탐색하여 벽을 통과하거나 허공에서 멈추는 문제가 발생합니다.

9-3. NavMesh 리빌드 타이밍

Simple Link의 PointLinks 배열을 런타임에 변경해도 NavMesh가 자동으로 업데이트되지 않습니다. 런타임에 링크 정보를 바꿔야 한다면 반드시 Smart Link를 사용해야 합니다. Simple Link는 NavMesh 빌드 시에 구워지는 정적 데이터입니다.

9-4. 에이전트 캡슐 크기와 링크 위치

NavLink의 시작/끝 지점은 에이전트 캡슐의 중심 기준으로 계산됩니다. 링크 끝 지점이 NavMesh 표면에서 너무 높거나 낮으면 PathFollowingComponent가 도착 판정에 실패할 수 있습니다. 끝 지점을 NavMesh 표면 위 에이전트 반 높이 정도에 배치하는 것이 안전합니다.

9-5. 다수의 에이전트와 Smart Link 동시 진입

여러 AI 에이전트가 동시에 같은 Smart Link에 진입할 경우, OnSmartLinkReached 콜백이 동시에 여러 번 호출됩니다. 콜백 내부에서 사용하는 멤버 변수(예: PendingController)가 단일 인스턴스를 저장하는 구조라면 먼저 진입한 에이전트의 참조가 덮어씌워질 수 있습니다. 에이전트 참조를 배열 또는 맵으로 관리하여 다중 에이전트를 처리하셔야 합니다.

9-6. World Partition 환경에서의 주의

World Partition이 활성화된 레벨에서는 NavLink Proxy 액터가 스트리밍 아웃될 수 있습니다. 스트리밍 아웃된 NavLink는 NavMesh 경로 계산에서 제외되므로, 영구적으로 필요한 NavLink는 Always Loaded 데이터 레이어에 배치하거나 NavMesh 빌드 범위 설정을 확인하셔야 합니다.

 

 

NavLink는 NavMesh가 물리적으로 표현하지 못하는 이동 구간을 경로 탐색 그래프에 명시적으로 추가하는 기능입니다. Simple Link로 정적인 연결 구간을 정의하고, Smart Link의 콜백 시스템으로 실제 이동 처리(점프, 낙하, 워프)를 구현하는 두 계층 구조를 이해하는 것이 핵심입니다. 플랫폼 이동이 있는 액션 게임, 레벨 구조가 복잡한 메트로배니아, 또는 다층 구조의 건물 내부를 탐색해야 하는 AI를 구현할 때 이 기능을 적극적으로 활용하시기 바랍니다.

반응형