Customizing Path Following

There are many possible ways for you to customize the path following system for your characters, ranging from simple (such as calling class methods) to complex (re-implementing core classes). This topic, and the other topics in this section, outline some of the most common options and strategies for controlling the operation of the various elements of path following system.

Customizing Bot configuration parameters and class methods

Each Bot accepts a wide range of configuration parameters that control the way it finds and follows paths.

Setting parameters

You can set your Bot configuration parameters in a BotConfig object, which you provide at initialization time in your call to Bot::Init(). This approach allows you to create and set up a single configuration object, which you can use to set up multiple game characters with an identical set of configuration parameters.

The BotConfig class contains some simple data members, and groups other parameters by functional category into subordinate configuration objects.

For example:

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);

All of the configuration parameters in the BotConfig can also be set dynamically for each character, by calling methods of the Bot API.

Simple data members of the BotConfig are exposed directly through methods of the Bot class. For the subordinate configuration objects exposed by the BotConfig, the Bot class also exposes accessor methods to get and set the current instances.

For example:

bot->SetEnableAvoidance(true);

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

Physical dimensions

  • You can set the radius of the Bot in BotConfig::m_radius, or by calling Bot::SetRadius(). Note that the value you set here defines the width of the character only for the purposes of the dynamic avoidance system, and for visual debugging in the Navigation Lab. For the purposes of path finding and path following, the radius of the character is assumed to be the radius used to generate the NavMesh used by the Bot. The actual radius of your game character should always be close to the value used to generate the NavMesh, or you may encounter problems with your character bumping or passing through the static geometry.
  • You can set the height of the Bot in BotConfig::m_height, or by calling Bot::SetHeight(). This value is only used for visual debugging in the Navigation Lab.

Path finding settings

Path progress settings

You can use the PathProgressConfig class to set some parameters that determine how the path following system tracks the progress of the Bot along its path.

For example, if you use the check point system extensively, you may want to customize the PathProgressConfig::m_checkPointRadius value, which determines how close the Bot must come to a check point before it is allowed to continue past the check point. See also Using Check Points.

You can set up the PathProgressConfig in BotConfig::m_pathProgressConfig or by calling Bot::SetPathProgressConfig()

Trajectory computation settings

  • You can choose which system of trajectory computation your character will use in BotConfig::m_trajectoryMode or by calling Bot::SetTrajectoryMode(). Note that if you switch from the shortcut system to the channel system, you will have to launch a new path recomputation in order to generate the channel data needed by the channel trajectory computer. Switching from the channel trajectory system to the shortcut system takes effect immediately.
  • Parameters for controlling the shortcut trajectory system are gathered into the ShortcutTrajectoryConfig class, which you can set up in BotConfig::m_shortcutTrajectoryConfig or by calling Bot::SetShortcutTrajectoryConfig().

    For example, you can control how often the Bot checks for a new shortcut, how far away the shortcut can be, the spacing of the candidate points it checks along the path, etc.

  • Parameters for controlling the channel trajectory system are gathered into the SplineTrajectoryConfig class, which you can set up in BotConfig::m_splineTrajectoryConfig or by calling Bot::SetSplineTrajectoryConfig().

    For example, you can control how close the Bot is allowed to come to the channel boundaries on corners, how often it should recompute its spline, the desired length of each spline, etc.

  • Both trajectory computation systems use parameters from the LocomotionModelConfig class to determine how the final velocity of the Bot should be calculated based on the current target point or the current spline being followed. You can set up this class in BotConfig::m_locomotionModelConfig or by calling Bot::SetLocomotionModelConfig().

    The maximum desired speed for the character (LocomotionModelConfig::m_maxDesiredLinearSpeed) is also exposed through shortcuts accessors in the Bot class, Bot::SetMaxDesiredLinearSpeed() and Bot::GetMaxDesiredLinearSpeed()

  • Both trajectory computation systems skip recomputing the trajectory if the character has not moved more than a minimal distance threshold since the last computation. You can set this threshold in the Bot class using Bot::SetTrajectoryMinimalMoveDistance() and Bot:: GetTrajectoryMinimalMoveDistance().

Dynamic avoidance settings

  • You can enable and disable dynamic avoidance for your characters in BotConfig::m_enableAvoidance or by calling Bot::SetEnableAvoidance(). The dynamic avoidance system used by the Trajectory class helps your characters avoid collisions with other nearby characters and dynamic obstacles. However, it increases the computational load of the path following process at each frame. If you have very large crowds, and your level of detail is low enough that the occasional collision is not noticeable, you may want to disable the avoidance system.
  • The ColliderCollectorConfig class contains parameters that control the distance at which other characters and dynamic obstacles get considered as potential colliders, and how often the system refreshes its list of potential colliders. You can set up in BotConfig::m_colliderCollectorConfig or by calling Bot::SetColliderCollectorConfig().
  • The AvoidanceConfig class contains parameters that control the way a character acts when it detects a potential collision, and how it modifies its velocity to attempt to avoid the collision. You can set up in BotConfig::m_avoidanceConfig or by calling Bot::SetAvoidanceConfig().

Customizing the NavigationProfile

A NavigationProfile is an object factory that provides instances of classes used during path computations and path following. It has three main purposes:

The World creates and maintains a default NavigationProfile at initialization time, which provides access to instances of all the default path finding and path following classes supplied with the SDK. Every Bot is automatically set up at initialization time to use this default NavigationProfile, unless you specify otherwise.

Note that the default NavigationProfile is fully functional out of the box, and the set of default objects that it serves is highly customizable (see the previous section for details). You can certainly continue to use this default profile for your project. You only need to use a different NavigationProfile if you have a particular need to override the default implementation for one or more of the classes it serves.

The most common needs for user customization in the NavigationProfile include:

In all of these cases, you would need to set up a custom class of NavigationProfile to serve your custom implementation.

Setting up a custom NavigationProfile

NavigationProfiles are managed centrally by the World. Each time you set up a new NavigationProfile, the World assigns it a unique ID. You use this ID value to set up other objects (typically Bots and path finding queries) with the profile they should use.

Note that the unique ID of the default NavigationProfile is always 0.

To set up a custom NavigationProfile for a Bot:

  1. Write your custom implementation of the interface that you want your NavigationProfile to serve, or your custom implementation of the TraverseLogic interface.
  2. Write a new class that derives from NavigationProfile, and template the base class to the class of TraverseLogic you want it to use. This may be your custom class if you want to control forbidding and preferring NavTags, otherwise it should be the default SimpleTraverseLogic class supplied with the SDK.
  3. If you want to use a custom implementation of any of the interfaces served by the NavigationProfile, re-implement the appropriate virtual method to return a pointer to an object of your custom class.
  4. After you initialize your World, create an instance of your NavigationProfile class, and pass a pointer to it in a call to World::AddNavigationProfile(). This method returns the unique ID assigned to the profile. Keep track of this ID value.
  5. You can set up a Bot to use your new profile in BotInitConfig::m_startNewPathNavigationProfileId, or by calling Bot::SetNewPathNavigationProfileId(). Any time you request a path calculation by calling Bot::ComputeNewPathToDestination(), your custom profile will now be used. See also the "basic" path calculation method under Getting a Static Path.

    Note, however, that if you set up a Bot with a custom path finding query or your own pre-planned path, as described in the "advanced" path calculation methods on that page, the Bot will automatically use the profile ID set for the query or the Path object while it follows that path. If you want the Bot to use your custom profile, you must also set up your custom query or Path object with the ID of your custom profile by calling its IPathFinderQuery::SetNavigationProfileId() or Path::SetNavigationProfileId() method.

For example:

// 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();
...

Taking temporary control over the Bot

At times, you may want to temporarily take complete control over the movement of your character in order to play a particular animation (climbing a ladder, jumping, etc.), in order to play a cutscene, etc.

To take temporary control, you could simply continue your path following as normal, but ignore the velocity computed by the path following system.

However, it is recommended that you temporarily disable path following by calling both Bot::SetDoValidateCheckPoint() and Bot::SetDoComputeTrajectory(). Call them again to re-activate path following when you no longer need total control over the movements of your character. For example:

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);
    }
}

Note that while the path following system is disabled in this way, your Bot is rendered in blue while visual debugging in the Navigation Lab instead of the default yellow.

Note also that depending on how long you keep control of your character's movement, and how far you drive it, you may need to force the current target point forward along the path when you release control. See also Creating Smart Objects.