icon안동민 개발노트

내비게이션 메시와 경로 찾기


 언리얼 엔진의 내비게이션 시스템은 AI 캐릭터의 이동과 경로 찾기를 위한 강력한 도구를 제공합니다.

 이 절에서는 C++ 관점에서 내비게이션 시스템을 활용하는 방법을 살펴보겠습니다.

내비게이션 메시의 개념과 생성

 내비게이션 메시(NavMesh)는 AI 캐릭터가 이동할 수 있는 영역을 표현하는 3D 구조입니다.

// NavMesh 생성 및 설정
UCLASS()
class AMyNavMeshBoundsVolume : public AVolume
{
    GENERATED_BODY()
 
public:
    AMyNavMeshBoundsVolume()
    {
        GetBrushComponent()->SetCollisionProfileName(TEXT("NoCollision"));
        GetBrushComponent()->SetCanEverAffectNavigation(true);
    }
};
 
// 레벨에 NavMesh 자동 생성 설정
void AMyGameMode::BeginPlay()
{
    Super::BeginPlay();
 
    UNavigationSystemV1::SetNavigationAutoUpdateEnabled(true);
}

 NavMesh를 사용하여 유효한 위치를 찾거나 경로를 계산할 수 있습니다.

bool AMyAIController::FindRandomReachablePoint(FVector& OutResult)
{
    UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent<UNavigationSystemV1>(GetWorld());
    if (NavSys)
    {
        FNavLocation RandomPoint;
        if (NavSys->GetRandomReachablePointInRadius(GetPawn()->GetActorLocation(), 1000.0f, RandomPoint))
        {
            OutResult = RandomPoint.Location;
            return true;
        }
    }
    return false;
}
 
bool AMyAIController::FindPathToLocation(const FVector& TargetLocation)
{
    UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent<UNavigationSystemV1>(GetWorld());
    if (NavSys)
    {
        FPathFindingQuery Query;
        Query.StartLocation = GetPawn()->GetActorLocation();
        Query.EndLocation = TargetLocation;
        Query.NavData = NavSys->GetDefaultNavDataInstance();
        Query.SetAllowPartialPaths(true);
 
        FPathFindingResult Result = NavSys->FindPathSync(Query);
        if (Result.IsSuccessful())
        {
            // 경로 사용
            return true;
        }
    }
    return false;
}

동적 장애물 처리

 동적으로 변화하는 환경에 대응하기 위해 동적 장애물을 처리할 수 있습니다.

UCLASS()
class AMyDynamicObstacle : public AActor
{
    GENERATED_BODY()
 
public:
    AMyDynamicObstacle()
    {
        PrimaryActorTick.bCanEverTick = true;
        RootComponent = CreateDefaultSubobject<USceneComponent>(TEXT("RootComponent"));
    }
 
    virtual void Tick(float DeltaTime) override
    {
        Super::Tick(DeltaTime);
        UpdateNavOctree();
    }
 
private:
    void UpdateNavOctree()
    {
        UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent<UNavigationSystemV1>(GetWorld());
        if (NavSys)
        {
            NavSys->UpdateNavOctree(this);
        }
    }
};

AI 캐릭터의 경로 따라가기

 계산된 경로를 따라 AI 캐릭터를 이동시킬 수 있습니다.

void AMyAIController::FollowPath(const TArray<FNavPathPoint>& PathPoints)
{
    if (PathPoints.Num() > 0)
    {
        CurrentPathIndex = 0;
        MoveToLocation(PathPoints[CurrentPathIndex].Location);
    }
}
 
void AMyAIController::OnMoveCompleted(FAIRequestID RequestID, const FPathFollowingResult& Result)
{
    Super::OnMoveCompleted(RequestID, Result);
 
    if (Result.IsSuccess() && ++CurrentPathIndex < PathPoints.Num())
    {
        MoveToLocation(PathPoints[CurrentPathIndex].Location);
    }
}

경로 재계산 트리거 설정

 환경 변화에 따라 경로를 재계산하는 트리거를 설정할 수 있습니다.

UCLASS()
class AMyAIController : public AAIController
{
    GENERATED_BODY()
 
public:
    virtual void Tick(float DeltaTime) override;
 
private:
    FVector TargetLocation;
    float PathUpdateInterval = 1.0f;
    float TimeSinceLastPathUpdate = 0.0f;
 
    void UpdatePath();
};
 
void AMyAIController::Tick(float DeltaTime)
{
    Super::Tick(DeltaTime);
 
    TimeSinceLastPathUpdate += DeltaTime;
    if (TimeSinceLastPathUpdate >= PathUpdateInterval)
    {
        UpdatePath();
        TimeSinceLastPathUpdate = 0.0f;
    }
}
 
void AMyAIController::UpdatePath()
{
    if (FindPathToLocation(TargetLocation))
    {
        // 새 경로 사용
    }
}

복잡한 환경에서의 효율적인 내비게이션 전략

 복잡한 환경에서는 계층적 내비게이션 시스템을 구현할 수 있습니다.

UCLASS()
class UHierarchicalNavigation : public UObject
{
    GENERATED_BODY()
 
public:
    FVector FindPathInComplexEnvironment(const FVector& Start, const FVector& End);
 
private:
    TArray<FVector> FindHighLevelPath(const FVector& Start, const FVector& End);
    TArray<FVector> RefinePathSegment(const FVector& SegmentStart, const FVector& SegmentEnd);
};
 
FVector UHierarchicalNavigation::FindPathInComplexEnvironment(const FVector& Start, const FVector& End)
{
    TArray<FVector> HighLevelPath = FindHighLevelPath(Start, End);
    TArray<FVector> DetailedPath;
 
    for (int32 i = 0; i < HighLevelPath.Num() - 1; ++i)
    {
        TArray<FVector> SegmentPath = RefinePathSegment(HighLevelPath[i], HighLevelPath[i + 1]);
        DetailedPath.Append(SegmentPath);
    }
 
    return DetailedPath[0]; // 다음 목적지 반환
}

내비게이션 시스템의 성능 최적화 기법

  1. NavMesh 타일 크기 최적화
void AMyGameMode::OptimizeNavMesh()
{
    UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent<UNavigationSystemV1>(GetWorld());
    if (NavSys)
    {
        ARecastNavMesh* NavMesh = Cast<ARecastNavMesh>(NavSys->GetDefaultNavDataInstance());
        if (NavMesh)
        {
            NavMesh->CellSize = 30.0f; // 타일 크기 조정
            NavMesh->TileSizeUU = 1000.0f; // 타일 크기 단위 조정
            NavMesh->RebuildAll();
        }
    }
}
  1. 동적 NavMesh 업데이트 최적화
void AMyGameMode::OptimizeDynamicNavMeshUpdates()
{
    UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent<UNavigationSystemV1>(GetWorld());
    if (NavSys)
    {
        NavSys->SetMaxSimultaneousTileGenerationJobsCount(4);
        NavSys->SetDirtyAreaWarningSizeThreshold(500.0f);
    }
}

대규모 월드에서의 내비게이션 관리 전략

 대규모 월드에서는 스트리밍 레벨과 함께 내비게이션 데이터를 관리해야 합니다.

UCLASS()
class AMyLevelStreamingVolume : public AVolume
{
    GENERATED_BODY()
 
public:
    UPROPERTY(EditAnywhere, Category = "Streaming")
    FName LevelToStream;
 
    virtual void ActorEnteredVolume(AActor* Other) override;
    virtual void ActorLeavingVolume(AActor* Other) override;
};
 
void AMyLevelStreamingVolume::ActorEnteredVolume(AActor* Other)
{
    ULevelStreamingDynamic::LoadLevelInstance(GetWorld(), LevelToStream, FVector::ZeroVector, FRotator::ZeroRotator);
}
 
void AMyLevelStreamingVolume::ActorLeavingVolume(AActor* Other)
{
    ULevelStreamingDynamic::UnloadStreamedLevel(GetWorld(), LevelToStream);
}

동적 환경 변화에 대응

 동적으로 변화하는 환경에 대응하기 위해 NavMesh를 실시간으로 업데이트할 수 있습니다.

UCLASS()
class AMyDynamicEnvironmentManager : public AActor
{
    GENERATED_BODY()
 
public:
    void UpdateEnvironment(const TArray<AActor*>& ChangedActors);
 
private:
    void UpdateNavMeshForActors(const TArray<AActor*>& Actors);
};
 
void AMyDynamicEnvironmentManager::UpdateEnvironment(const TArray<AActor*>& ChangedActors)
{
    UpdateNavMeshForActors(ChangedActors);
}
 
void AMyDynamicEnvironmentManager::UpdateNavMeshForActors(const TArray<AActor*>& Actors)
{
    UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent<UNavigationSystemV1>(GetWorld());
    if (NavSys)
    {
        for (AActor* Actor : Actors)
        {
            NavSys->UpdateNavOctree(Actor);
        }
    }
}

 언리얼 엔진의 내비게이션 시스템은 AI 캐릭터의 이동과 경로 찾기를 위한 강력한 도구를 제공합니다. NavMesh를 활용하여 복잡한 환경에서도 효율적인 경로 찾기가 가능하며, 동적 장애물 처리를 통해 변화하는 환경에도 대응할 수 있습니다.

 성능 최적화를 위해서는 NavMesh의 타일 크기와 업데이트 빈도를 적절히 조정해야 합니다. 대규모 월드에서는 스트리밍 레벨과 함께 내비게이션 데이터를 관리하여 메모리 사용을 최적화할 수 있습니다.

 동적 환경 변화에 대응하기 위해서는 실시간 NavMesh 업데이트 시스템을 구현해야 합니다. 이를 통해 이동 가능 영역의 변화를 즉시 반영하여 AI 캐릭터의 정확한 경로 찾기를 보장할 수 있습니다.

 마지막으로, 복잡한 환경에서는 계층적 내비게이션 시스템을 구현하여 경로 찾기의 효율성을 높일 수 있습니다. 이는 대규모 지역의 경로를 먼저 계산한 후, 세부 경로를 정교화하는 방식으로 작동합니다.

 이러한 기법들을 적절히 조합하여 사용하면, 다양한 규모와 복잡성을 가진 게임 환경에서 효율적이고 안정적인 AI 내비게이션 시스템을 구축할 수 있습니다.