NavTag 금지, 회피 및 선호

사용자 정의 데이터로 지세의 영역에 태그를 지정하는 데 NavTag를 사용하면 이 NavTag 중 어떤 것이 각 캐릭터에 대해 유효한 보행 가능 지세로 간주되는지 결정할 수 있습니다. 이는 해당 캐릭터가 동일한 Database(따라서 동일한 NavData 세트)를 사용하는 경우에도 마찬가지입니다. 또한 서로 다른 봇이 서로 다른 NavTag 가로지르기를 선호하거나 회피하는 범위를 사용자 정의할 수 있습니다.

예를 들어 다음 이미지에서는 거리에서 가능한 세 가지 경로를 보여줍니다.

  1. 경로 번호 1에서는 보라색 NavTag를 가로질러 이동하기가 금지됩니다. 따라서 경로가 횡단 보도에서 거리를 교차하도록 강제합니다.
  2. 경로 번호 2에서는 보라색 NavTag가 이동 가능으로 간주되며 태그가 지정되지 않은 영역을 통과하여 이동하는 것과 비용이 동일합니다. 따라서 시작부터 대상까지의 직접 경로가 허용됩니다.
  3. 경로 번호 3에서는 보라색 NavTag가 이동 가능으로 간주되지만 가로지르는 비용이 태그가 지정되지 않은 파란색 영역보다 2배 높습니다. 따라서 최종 경로에서 영역 통과를 최소화하도록 합니다. 거리에서 가장 넓은 부분을 교차하는 대신 경로는 횡단 보도 사용과 비교하여 지름길을 나타내는 더 좁은 교차 지점을 찾습니다.

단계 1: 지세 태그 지정

데이터 생성 단계 또는 게임 과정에서 동적으로 지세에 NavTag를 적용하는 방법에 대한 자세한 내용은 사용자 정의 데이터로 태그 지정을(를) 참조하십시오.

단계 2: TraverseLogic 작성

자체 경로에서 NavTag를 금지, 선호 또는 회피하려면 사용자 정의 TraverseLogic 클래스를 작성해야 합니다. 이 클래스는 다음과 같은 경우에 사용됩니다.

NavMesh에 대해 다른 종류의 쿼리를 실행할 때 이 동일한 TraverseLogic 클래스를 사용하여 NavTag를 테스트할 수도 있습니다. 예를 들어 RayCastQuery를 실행하여 두 점 사이의 직선 경로를 따라 장애물이 있는지 여부를 검색하는 경우 사용할 클래스의 인스턴스로 쿼리를 설정하여 통과할 수 있는 것으로 간주되는 서로 다른 NavTag를 결정할 수 있습니다.

세 가지 TraverseLogic 인터페이스는 다음과 같습니다. 기본 구현에는 3가지 다른 레벨의 복잡성이 있으며 요구 사항에 맞는 하나를 선택할 수 있습니다.

TraverseLogic 중 하나가 NavigationProfile 또는 쿼리에 템플릿 매개변수로 전달되면 해당 코드가 자동으로 생성됩니다. 단계 3 TraverseLogic 설정을 참조하십시오.

삼각형의 비용 승수 계산 및 저장

TraverseLogicWithCostPerTriangle에서 삼각형의 비용 승수가 단일 PathFinderQuery 동안 여러 번 요청됩니다. 잘못된 구현으로 인한 성능 문제를 방지하기 위해 TriangleCostMap 클래스가 일부 삼각형의 비용 승수를 계산하고 저장하기 위해 사용되며 쿼리에서 액세스할 수 있습니다. ITriangleCostMap에서 파생되는 고유한 TriangleCostMap 클래스를 작성해야 합니다. 한 개의 순수한 가상 Recompute() 함수만 있습니다.

ITriangleCostMap은 사용하기 전에 BindToDatabase() 함수를 통해 데이터베이스에 바인딩되어야 합니다. 데이터베이스는 동적 NavMesh와 같은 데이터의 삼각 측량에서 변경된 내용이 있는 경우 Recompute() 함수를 내부적으로 호출할 수 있습니다. 이 함수는 해당 비용 승수를 업데이트하기 위해 클라이언트에서 호출할 수 있지만 자주 그래서는 안 됩니다. 다음 도우미 함수를 호출할 수 있습니다. Recompute()에서 ITriangleCostMap::InitAndBrowseAllTrianglesInBox3f()ITriangleCostMap::InitAndPropagateInTrianglesFromPosInVolume()은 삼각형의 비용 승수를 자동으로 초기화하고 설정합니다. 자세한 내용은 itrianglecostmap.h를 참조하십시오.

삼각형에 비용을 설정하는 고유한 방법을 작성할 수 있지만 비용 승수(기본 비용 승수는 1.f)를 변경하려면 삼각형에서 SetCostMultiplier() 함수를 호출하기 전에 InitCostMapForBox3f() 함수를 호출해야 합니다.

일부 봇이 삼각형에서 경로를 찾고 있는 동안에는 삼각형의 비용을 변경해서는 안 됩니다. TriangleCostMap을 사용하는 봇의 비동기 경로 계산은 취소하는 것이 좋습니다. TriangleCostMap을 자주 업데이트해서는 안 됩니다. 그렇지 않으면 봇이 두 업데이트 사이에 충분한 시간이 없어서 경로를 찾지 못할 수도 있습니다. 다음 코드 예는 경로 계산을 취소하기 위해 Recompute() 함수에서 수행할 수 있는 내용을 표시합니다.

for (KyUInt32 i = 0; i < gameWorld->GetBots().GetCount(); ++i)
    {
        GameBot* gamebot = gameWorld->GetBots()[i];
        Kaim::Bot* navBot = gamebot->GetBot();
        if (navBot->IsComputingNewPath())
            navBot->CancelAsyncPathComputation();
    }

이미 경로를 따르고 있는 봇이 업데이트된 TriangleCostMap과 일관된 경로를 계속 유지하도록 하기 위해 경로 계산을 다시 실행해야 할지 여부를 결정할 수 있습니다.

자체 TraverseLogic을 설정하지 않은 경우 기본적으로 사용되는 DefaultTraverseLogic 클래스를 참조하십시오. DefaultTraverseLogic 또는 SimpleTraverseLogic에서 직접 사용자 정의 클래스를 파생시키는 경우 원하는 동작을 얻기 위해 재정의 메서드만 구현해야 합니다.

NavTag 전환 제어

기본적으로 DefaultTraverseLogic 또는 SimpleTraverseLogic 클래스는 금지된 NavTag를 통과하는 모든 이동을 방지합니다.

그러나 특정 경우에는 한 방향으로만 또는 특정 다른 NavTag에서만 NavTag를 가로지르는 탐색을 금지할 수 있습니다. 이 방법에서는 NavTag가 적용된 영역을 가로지르는 이동은 허용하지만 NavTag 경계를 가로지르는 이동은 금지합니다.

이런 경우 다음을 수행해야 합니다.

  • SimpleTraverseLogic에서 직접 클래스를 파생시키고 Kaim::LogicDoUseCanEnterNavTag를 템플릿 매개변수로 제공합니다. 예를 들면 다음과 같습니다.
    class MyTraverseLogicClass : public Kaim::SimpleTraverseLogic<Kaim::LogicDoUseCanEnterNavTag>
    {
        ...
    };

    또는 기존 클래스 중 하나에서 파생시키지 않고 처음부터 직접 TraverseLogic 클래스를 작성하는 경우 클래스에 다음 줄을 포함합니다.

    typedef Kaim::LogicDoUseCanEnterNavTag CanEnterNavTagMode;
  • NavTag를 가로지르는 이동을 허용하도록 CanTraverse() 메서드의 구현을 설정합니다.
  • 이전 NavTag에서 NavTag로의 전환을 허용할지 여부를 결정하도록 CanEnterNavTag() 메서드의 구현을 설정합니다.

최상의 성능을 제공하려면 특정 게임 플레이 사용 사례가 있는 경우 TraverseLogic만 설정하여 NavTag 전환을 확인해야 합니다.

사용자 데이터

TraverseLogic 인터페이스의 모든 메서드는 정적이므로 특정 오브젝트나 상태에 연결되어 있지 않습니다. 따라서 런타임 시 봇 및 쿼리에서 사용할 클래스 인스턴스를 만들 필요가 없으므로 런타임 메모리가 절약됩니다.

그러나 TraverseLogic에서 NavTag를 금지해야 하는지 여부 또는 다른 환경의 다른 NavTag에 할당해야 하는 비용 승수를 적절히 결정할 수 있도록 하려면 오브젝트의 특정 인스턴스에 연결된 일부 종류의 데이터를 사용해야 할 수 있습니다. 이러한 이유로 TraverseLogic 메서드는 모두 빈 포인터를 허용합니다. TraverseLogic을 사용하도록 템플릿화되는 각 클래스(특히 Bot 클래스와 AStarQueryRayCastQuery와 같은 쿼리 클래스)는 Bot 또는 Query가 TraverseLogic 인터페이스에서 메서드를 호출해야 할 때마다 전달하는 사용자 데이터를 설정하고 검색하는 데 사용할 수 있는 액세서를 제공합니다. 예를 들면 다음과 같습니다.

Bot::SetBotTraverseLogicUserData()
Bot::GetBotTraverseLogicUserData()

IQuery::SetTraverseLogicUserData()
IQuery::GetTraverseLogicUserData()

단계 3: TraverseLogic 설정

봇을 설정하려면

  1. NavigationProfile 클래스에서 파생되는 클래스를 직접 작성하고 TraverseLogic 클래스의 이름을 NavigationProfile의 템플릿 매개변수로 전달합니다. 필요 없는 경우 NavigationProfile 클래스의 가상 메서드를 모두 재정의할 필요는 없습니다. 예를 들면 다음과 같습니다.
    class MyNavigationProfile : public Kaim::NavigationProfile<MyTraverseLogicClass>
    {
    };
  2. NavigationProfile을 사용하도록 WorldBots를 설정합니다. 자세한 내용은 경로 따르기 사용자 정의을(를) 참조하십시오.

직접 처리할 쿼리를 설정하려면

제한 사항

비용 계산 방법

각 경로 세그먼트의 비용은 AStarQuery 또는 경로 따르기 시스템에 의해 내부적으로 실행되어 경로에 장애물이 없는지 테스트하는 RayCanGoQuery에 의해 개별적으로 계산됩니다. RayCanGoQuery에서 테스트한 선 세그먼트가 NavTag 경계를 교차할 때마다 쿼리는 해당 경계 점을 수직으로 선 세그먼트에 투영합니다. 그런 다음 선 세그먼트를 따르는 각 하위 세그먼트의 거리에 해당 NavTag에 할당된 비용 승수를 곱합니다.

예를 들어 다음 시나리오에서 RayCanGoQuery가 테스트한 선 세그먼트는 NavMesh의 삼각형을 통해 전파되므로 여러 개의 다른 NavTag를 교차합니다.

총 비용은 다음과 같이 계산됩니다.

                        ( Distance(a) * CostMultiplier(blue) )
                    +   ( Distance(b) * CostMultiplier(grey) )
                    +   ( Distance(c) * CostMultiplier(blue) )
                    + ( Distance(d) * CostMultiplier(purple) )
                    +   ( Distance(e) * CostMultiplier(blue) )
                    +   ( Distance(f) * CostMultiplier(grey) )
                    +   ( Distance(g) * CostMultiplier(blue) ) 

전체 경로의 비용을 결정할 때 이런 방식으로 경로의 각 세그먼트가 테스트되며 총 비용은 모든 세그먼트 비용의 합계입니다.

성능상의 이유로 비용을 측정하는 이 메서드는 NavMesh의 삼각형을 통과하는 경로가 실제로 적용된 정확한 거리를 사용하지 않으며 해당 경로를 따르는 동안 캐릭터가 이동하는 정확한 거리도 사용하지 않습니다. 그러나 근사값은 게임 플레이에 필요한 효과를 생성할 비용 승수 세트를 찾을 수 있을 만큼 충분히 가까워야 합니다.

자체 코드에서 직접 RayCanGoQuery를 실행하는 경우 테스트하는 선의 비용을 계산하도록 구성할 수 있습니다. RayCanGoQuery::SetComputeCostMode() 메서드를 호출하고 QUERY_COMPUTE_COST_ALONG_3DAXIS를 전달한 다음 NavTag의 이동 가능성 및 비용 승수를 결정하는 데 사용되는 TraverseLogic으로 쿼리를 설정해야 합니다. 쿼리를 실행한 후에는 RayCanGoQuery::GetComputedCost()를 호출하여 비용을 검색할 수 있습니다.

자습서 예

A* 쿼리에서 생성한 경로에 영향을 미치고, 경로 따르기 중 특정 NavTag를 금지하는 술어처럼 사용자 정의 TraverseLogic 클래스의 사용을 나타내는 작업 코드 예는 Tutorial_NavTag.cpp 파일을 참조하십시오.