경로 따르기 사용자 정의

캐릭터의 경로 따르기 시스템을 사용자 정의하는 방법에는 클래스 메서드 호출과 같은 간단한 방법부터 핵심 클래스 재구현과 같은 복잡한 방법까지 여러 가지가 있습니다. 이 항목과 이 섹션의 나머지 항목에서는 경로 따르기 시스템의 여러 요소의 작동을 제어하는 가장 일반적인 옵션 및 전략 중 몇 가지를 간략하게 설명합니다.

Bot 구성 매개변수 및 클래스 메서드 사용자 정의

각 봇은 경로를 찾고 따르는 방법을 제어하는 광범위한 구성 매개변수를 수용합니다.

매개변수 설정

Bot 구성 매개변수는 BotConfig 오브젝트에서 설정할 수 있으며, 초기화 시 Bot::Init()를 호출하여 제공합니다. 이 방법을 사용하면 단일 구성 오브젝트를 만들고 설정할 수 있으며, 이 오브젝트를 사용하여 동일한 구성 매개변수 세트로 이루어진 여러 게임 캐릭터를 설정할 수 있습니다.

BotConfig 클래스는 몇 가지 단순 데이터 구성원을 포함하며 다른 매개변수를 기능 범주에 따라 하위 구성 오브젝트로 그룹화합니다.

예를 들면 다음과 같습니다.

Kaim::BotConfig botConfig;
botConfig.m_enableAvoidance = true;
botConfig.m_shortcutTrajectoryConfig.m_maxDistanceFromBot = 20.0f;
...

Kaim::Ptr<Kaim::Bot> bot = *KY_NEW Kaim::Bot();
bot->Init(botInitConfig, botConfig);

BotConfig의 모든 구성 매개변수는 Bot API의 메서드를 호출하여 각 캐릭터에 대해 동적으로 설정할 수도 있습니다.

BotConfig의 단순 데이터 구성원은 Bot 클래스의 메서드를 통해 직접 노출됩니다. 또한 Bot 클래스는 BotConfig에 의해 노출된 하위 구성 오브젝트에 대해 데이터 액세서 메서드를 노출하여 현재 인스턴스를 가져오고 설정합니다.

예를 들면 다음과 같습니다.

bot->SetEnableAvoidance(true);

Kaim::ShortcutTrajectoryConfig shortcutConfig;
shortcutConfig.SetDefaults();
shortcutConfig.m_maxDistanceFromBot = 20.0f;
bot->SetShortcutTrajectoryConfig(shortcutConfig);

물리적 치수

  • Bot의 반지름은 BotConfig::m_radius에서 설정하거나 Bot::SetRadius()를 호출하여 설정할 수 있습니다. 여기서 설정하는 값은 동적 회피 시스템 및 Navigation Lab의 시각적 디버깅을 위해서만 캐릭터의 너비를 정의합니다. 경로 찾기 및 경로 따르기에 사용할 캐릭터의 반지름은 Bot에서 사용하는 NavMesh를 생성하는 데 사용된 반지름으로 간주됩니다. 게임 캐릭터의 실제 반지름은 항상 NavMesh를 생성하는 데 사용되는 값과 가까워야 합니다. 그렇지 않으면 정적 형상을 통과하거나 충돌하는 캐릭터에서 문제가 발생할 수 있습니다.
  • Bot의 높이는 BotConfig::m_height에서 설정하거나 Bot::SetHeight()를 호출하여 설정할 수 있습니다. 이 값은 Navigation Lab의 시각적 디버깅에만 사용됩니다.

경로 찾기 설정

경로 진행률 설정

PathProgressConfig 클래스를 사용하면 경로 따르기 시스템이 경로를 따라 이동하는 Bot의 진행률을 추적하는 방법을 결정하는 일부 매개변수를 설정할 수 있습니다.

예를 들어 체크 포인트 시스템을 광범위하게 사용하는 경우 PathProgressConfig::m_checkPointRadius 값을 사용자 정의하여 Bot이 체크 포인트에 얼마나 가깝게 접근해야 해당 체크 포인트를 지나서 계속 갈 수 있는지를 결정할 수 있습니다. (체크 포인트 사용 참조).

PathProgressConfigBotConfig::m_pathProgressConfig에서 설정하거나 Bot::SetPathProgressConfig()를 호출하여 설정할 수 있습니다.

궤적 계산 설정

  • 캐릭터가 사용할 궤적 계산 시스템은 BotConfig::m_trajectoryMode에서 선택하거나 Bot::SetTrajectoryMode()를 호출하여 선택할 수 있습니다. 바로 가기 시스템에서 채널 시스템으로 전환할 경우 채널 궤적 컴퓨터에 필요한 채널 데이터를 생성하려면 경로 다시 계산을 새로 시작해야 합니다. 채널 궤적 시스템에서 바로 가기 시스템으로의 전환은 바로 적용됩니다.
  • 바로 가기 궤적 시스템을 제어하는 매개변수는 ShortcutTrajectoryConfig 클래스로 수집되며, BotConfig::m_shortcutTrajectoryConfig에서 설정하거나 Bot::SetShortcutTrajectoryConfig()를 호출하여 설정할 수 있습니다.

    예를 들어 Bot이 새 바로 가기를 확인하는 빈도, 바로 가기를 얼마나 멀리 배치할 수 있는지 정도, 경로를 따라 확인되는 후보 포인트의 간격 등을 제어할 수 있습니다.

  • 채널 궤적 시스템을 제어하는 매개변수는 SplineTrajectoryConfig 클래스로 수집되며, BotConfig::m_splineTrajectoryConfig에서 설정하거나 Bot::SetSplineTrajectoryConfig()를 호출하여 설정할 수 있습니다.

    예를 들어 Bot이 모서리의 채널 경계에 얼마나 가깝게 다가갈 수 있는지 정도, 해당 스플라인을 다시 계산해야 하는 빈도, 각 스플라인의 원하는 길이 등을 제어할 수 있습니다.

  • 두 궤적 계산 시스템 모두 LocomotionModelConfig 클래스의 매개변수를 사용하여 따르고 있는 현재 대상 포인트 또는 현재 스플라인에 따라 Bot의 최종 속도를 계산하는 방법을 결정합니다. 이 클래스는 BotConfig::m_locomotionModelConfig에서 설정하거나 Bot::SetLocomotionModelConfig()를 호출하여 설정할 수 있습니다.

    또한 캐릭터에 원하는 최대 속도(LocomotionModelConfig::m_maxDesiredLinearSpeed)는 바로 가기 액세서를 통해 Bot 클래스, Bot::SetMaxDesiredLinearSpeed()Bot::GetMaxDesiredLinearSpeed()에 노출됩니다.

  • 캐릭터가 마지막 계산 이후 최소 거리 임계값보다 많이 이동하지 않은 경우 두 궤적 계산 시스템은 모두 궤적 다시 계산을 건너뜁니다. Bot::SetTrajectoryMinimalMoveDistance()Bot:: GetTrajectoryMinimalMoveDistance()를 사용하여 Bot 클래스에서 이 임계값을 설정할 수 있습니다.

동적 회피 설정

  • 캐릭터에 대한 동적 회피는 BotConfig::m_enableAvoidance에서 또는 Bot::SetEnableAvoidance()를 호출하여 활성화하거나 비활성화할 수 있습니다. Trajectory 클래스에서 사용하는 동적 회피 시스템을 통해 캐릭터가 가까이 있는 다른 캐릭터 및 동적 장애물과의 충돌을 피할 수 있습니다. 그러나 이 경우 각 프레임에서 경로 따르기 프로세스의 계산 부하가 증가합니다. 군중이 매우 많고 간헐적인 충돌이 눈에 띄지 않을 정도로 정밀도 레벨이 낮은 경우 회피 시스템을 비활성화할 수도 있습니다.
  • ColliderCollectorConfig 클래스에는 다른 캐릭터와 동적 장애물이 잠재적 충돌체로 간주되는 거리 및 시스템이 잠재적 충돌체 목록을 새로 고치는 빈도를 제어하는 매개변수가 포함되어 있습니다. 이 클래스는 BotConfig::m_colliderCollectorConfig에서 설정하거나 Bot::SetColliderCollectorConfig()를 호출하여 설정할 수 있습니다.
  • AvoidanceConfig 클래스에는 캐릭터가 잠재적 충돌을 감지할 때 동작하는 방식 및 충돌을 피하기 위해 속도를 수정하는 방법을 제어하는 매개변수가 포함되어 있습니다. 이 클래스는 BotConfig::m_avoidanceConfig에서 설정하거나 Bot::SetAvoidanceConfig()를 호출하여 설정할 수 있습니다.

NavigationProfile 사용자 정의

NavigationProfile은 경로 계산 및 경로 따르기 중 사용되는 클래스의 인스턴스를 제공하는 오브젝트 공장이며, 다음과 같은 세 가지 주요 용도로 사용됩니다.

World는 초기화 시에 기본 NavigationProfile을 만들고 유지하여 SDK와 함께 제공된 모든 기본 경로 찾기 및 경로 따르기 클래스의 인스턴스에 대한 액세스 권한을 제공합니다. 다르게 지정하지 않는 한 모든 Bot은 초기화 시에 이 기본 NavigationProfile을 사용하도록 자동 설정됩니다.

기본 NavigationProfile은 바로 사용할 수 있는 모든 기능을 제공하며 NavigationProfile에서 제공하는 기본 오브젝트 세트는 원하는 대로 사용자 정의할 수 있습니다(자세한 내용은 이전 섹션 참조). 이 기본 프로필은 모든 프로젝트에 계속 사용할 수 있습니다. 이 프로필이 제공하는 하나 이상의 클래스에 대한 기본 구현을 특별히 재정의해야 하는 경우에만 다른 NavigationProfile을 사용해야 합니다.

NavigationProfile을 사용자 정의하는 가장 일반적인 경우는 다음과 같습니다.

이러한 모든 경우에 NavigationProfile의 사용자 정의 클래스를 설정하여 사용자 정의 구현을 제공해야 합니다.

사용자 정의 NavigationProfile 설정

NavigationProfile은 중앙에서 World에 의해 관리됩니다. 새 NavigationProfile을 설정할 때마다 World에서 고유 ID를 할당합니다. 이 ID 값을 사용하여 다른 오브젝트(일반적으로 Bot 및 경로 찾기 쿼리)를 해당 오브젝트가 사용해야 하는 프로필로 설정합니다.

기본 NavigationProfile의 고유 ID는 항상 0입니다.

Bot에 대해 사용자 정의 NavigationProfile을 설정하려면

  1. NavigationProfile에서 제공할 인터페이스의 사용자 정의 구현이나 TraverseLogic 인터페이스의 사용자 정의 구현을 작성합니다.
  2. NavigationProfile에서 파생되는 새 클래스를 작성하고 기본 클래스를 사용할 TraverseLogic의 클래스로 템플릿화합니다. 이 클래스는 NavTag 금지 및 우선 사용을 제어하려는 경우 사용자 정의 클래스가 될 수 있으며, 그렇지 않을 경우 SDK와 함께 제공된 기본 SimpleTraverseLogic 클래스가 되어야 합니다.
  3. NavigationProfile에서 제공하는 모든 인터페이스의 사용자 정의 구현을 사용하려면 사용자 정의 클래스의 오브젝트에 대한 포인터를 반환하도록 적절한 가상 메서드를 다시 구현합니다.
  4. World를 초기화한 후 NavigationProfile 클래스의 인스턴스를 만들고 World::AddNavigationProfile()을 호출할 때 해당 포인터를 전달합니다. 이 메서드는 프로필에 할당된 고유 ID를 반환합니다. 이 ID 값을 추적합니다.
  5. BotInitConfig::m_startNewPathNavigationProfileId에서 또는 Bot::SetNewPathNavigationProfileId()를 호출하여 Bot이 새 프로필을 사용하도록 설정할 수 있습니다. 이제 Bot::ComputeNewPathToDestination()을 호출하여 경로 계산을 요청할 때마다 사용자 정의 프로필이 사용됩니다. 정적 경로 가져오기에서 "기본" 경로 계산 방법을 참조하십시오.

    그러나 해당 페이지의 "고급" 경로 계산 방법에 설명된 대로 사용자 정의 경로 찾기 쿼리 또는 사용자 고유의 사전 계획된 경로를 사용하여 Bot을 설정하는 경우 Bot은 해당 경로를 따르는 동안 쿼리 또는 Path 오브젝트에 대해 설정된 프로필 ID를 자동으로 사용합니다. Bot이 사용자 정의 프로필을 사용하도록 하려면 IPathFinderQuery::SetNavigationProfileId() 또는 Path::SetNavigationProfileId() 메서드를 호출하여 사용자 정의 쿼리 또는 Path 오브젝트도 사용자 정의 프로필의 ID로 설정해야 합니다.

예를 들면 다음과 같습니다.

// The NavigationProfile in this example uses a custom TraverseLogic and a custom IPathEventListObserver.

class MyTraverseLogic : public Kaim::DefaultTraverseLogic
{
public:
    static KY_INLINE bool CanTraverseNavTriangle(void* userData, const Kaim::NavTriangleRawPtr& triangleRawPtr,
        const Kaim::NavTag& navTag, KyFloat32* costMultiplier)
    { ... }
};

class MyPathEventListener : public Kaim::IPathEventListObserver
{
public:
    virtual void OnPathEventListBuildingStageDone(Kaim::Bot* bot, Kaim::PathEventList& pathEventList, KyUInt32 firstIndexOfNewEvent, FirstIntervalStatus firstIntervalStatus)
    { ... }
    virtual void OnPathEventListDestroy(Kaim::Bot* bot, Kaim::PathEventList& pathEventList, DestructionPurpose destructionPurpose)
    { ... }
};

class MyNavigationProfile : public Kaim::NavigationProfile<MyTraverseLogic>
{
public:
    virtual Kaim::Ptr<Kaim::IPathEventListObserver> GetSharedPathEventListObserver()
    {
        if (m_myPathEventListener == KY_NULL)
            m_myPathEventListener = *KY_NEW MyPathEventListener();
        return m_myPathEventListener;
    }

public:
    Kaim::Ptr<MyPathEventListener> m_myPathEventListener;
};

...

// in your World initialization code:
const KyUInt32 databaseCount = 1;
m_world = *KY_NEW Kaim::World(databaseCount);

Kaim::Ptr<MyNavigationProfile> myNavigationProfile = *KY_NEW MyNavigationProfile();
KyUInt32 navigationProfileId = m_world->AddNavigationProfile(myNavigationProfile);
...

// in your Bot initialization code:

m_navBot = *KY_NEW Kaim::Bot;

Kaim::BotInitConfig botInitConfig;
botInitConfig.m_database = m_world->GetDatabase(0);
botInitConfig.m_startPosition = m_position;
botInitConfig.m_startNewPathNavigationProfileId = navigationProfileId;
m_navBot->Init(botInitConfig);

// We add the Bot to our Database.
m_navBot->AddToDatabase();
...

Bot 임시 제어

경우에 따라, 특정 애니메이션(사다리 오르기, 점프 등)이나 컷신(cutscene) 등을 재생하기 위해 캐릭터의 이동을 일시적으로 완전히 제어할 수 있습니다.

일시적으로 제어하기 위해 정상적으로 경로 따르기를 계속하되, 경로 따르기 시스템에 의해 계산된 속도를 무시할 수 있습니다.

그러나 Bot::SetDoComputeTargetOnPath()Bot::SetDoComputeTrajectory()를 모두 호출하여 경로 따르기를 일시적으로 비활성화하는 것이 좋습니다. 캐릭터의 이동을 더 이상 완전히 제어할 필요가 없는 경우 이 두 메서드를 다시 호출하여 경로 따르기를 다시 활성화할 수 있습니다. 예를 들면 다음과 같습니다.

void MyJumpObject::ManageTraversing()
{
    // Take control
    m_navBot->SetDoValidateCheckPoint(false);
    m_navBot->SetDoComputeTrajectory(false);
  
    ... // manage playing the jump animation

    if (animationIsFinished)
    {
        // Release control
        m_navBot->SetDoValidateCheckPoint(true);
        m_navBot->SetDoComputeTrajectory(true);
    }
}

경로 따르기 시스템이 이런 식으로 비활성화된 동안 Bot은 Navigation Lab에서 시각적으로 디버깅되면서 기본 노란색 대신 파란색으로 렌더링됩니다.

또한 캐릭터의 이동을 제어한 기간 및 캐릭터를 이동한 거리에 따라 제어를 해제할 때 현재 대상 점을 경로에서 앞으로 강제 이동해야 할 수도 있습니다 (스마트 오브젝트 만들기 참조).