Following a path is a much more complex problem than simply running an A* calculation to find the path. Path following implies interaction with locomotion and steering systems that are dynamic and sometimes limited. Dynamic events may require the path to change—for instance, if the NavData in the vicinity of the character changes. You need a dynamic avoidance system to avoid colliding with moving obstacles and other characters, etc.
In this phase of the integration, we will use the tools provided by the pathfollowing layer of Autodesk Navigation to get a character to find and follow a path.
The key component in the pathfollowing layer is the Bot class. Typically, you assign an instance of the Bot class to the classes that represent each of the game characters that you want to use the Autodesk Navigation path finding and path following system. (Other kinds of characters, such as player characters, will typically be represented by CylinderObstacles; see Using Dynamic Obstacles and TagVolumes.) The Bot uses a pathfinding query to compute a path to a destination you specify, and “follows” that path by calculating a desired velocity at each frame.
The lifecycle of the Bot implies direct interaction from the corresponding game entity in at least three places: initialization, updating at each frame, and destruction.
As you continue with your integration, you will see this same philosophy used for other classes, such as dynamic obstacles, TagVolumes, and points of interest.
When you initialize your character, you need to:
Create an instance of the BotInitConfig object, set up its class members as desired, and pass it in a call to Bot::Init(). At a minimum, you must set the BotInitConfig::m_database member to point to the Database that contains the NavData the character should use for path planning and path following.
Create an instance of an A* query using the Bot::ComputeAstarQueryAsync() method so that the Bot can compute paths. This is shown in the following example. The Bot::ComputeAstarQueryAsync() method cancels any previously running query before calling the current Bot's NavigationProfile to instantiate an AStarQuery using the ProfileID. Then, the Bot::InitAStarQuery() method is called to set up the query with the BotConfig. Finally, the Bot::ComputePathAsync() method is called to compute the query asynchronously and for changing the Bot's path properly.
Note: You can initialize an AStarQuery by yourself, and then call the Bot::ComputePathAsync() method. However, before calling the Bot::ComputePathAsync() method, you must check the computation status using the Bot::IsComputingPath() method.
Activate the Bot in its Database by calling Bot::AddToDatabase(), in order for it to be able to plan paths and in order for other bots to avoid collisions with it. This is usually done when you spawn your game character.
Each frame, your game character needs to interact with the Bot in order to start a new path computation when needed, to update the current state of the Bot, and to interpret and apply the results of the path following system.
When you are finished with your game character:
The following example shows a relatively simple use of the Bot class by the game character class MyGameEntity. After spawning, the character plans and follows path back and forth between its initial position and a given destination.
[code from Tutorial_FirstIntegration.cpp]
#include "gwnavruntime/world/bot.h" ... class MyGameEntity { public: MyGameEntity() : m_startPosition(0.0f, 0.0f, 0.0f) , m_destinationPosition(0.0f, 0.0f, 0.0f) , m_position(0.0f, 0.0f, 0.0f) , m_velocity(0.0f, 0.0f, 0.0f) , m_navBot(KY_NULL) {} void Initialize(Kaim::World* world, const Kaim::Vec3f& startPosition, const Kaim::Vec3f& destination); void Destroy(); void UpdateLogic(KyFloat32 simulationStepsInSeconds); void UpdatePhysics(KyFloat32 simulationStepsInSeconds); bool HasArrived(); public: Kaim::Vec3f m_startPosition; Kaim::Vec3f m_destinationPosition; Kaim::Vec3f m_position; Kaim::Vec3f m_velocity; Kaim::Ptr<Kaim::Bot> m_navBot; }; void MyGameEntity::Initialize(Kaim::World* world, const Kaim::Vec3f& startPosition, const Kaim::Vec3f& destination) { m_position = startPosition; m_startPosition = startPosition; m_destinationPosition = destination; m_navBot = *KY_NEW Kaim::Bot; // We initialize the Bot at our desired start position Kaim::BotInitConfig botInitConfig; botInitConfig.m_database = world->GetDatabase(0); botInitConfig.m_startPosition = m_position; m_navBot->Init(botInitConfig); ... // We add the Bot to our Database. m_navBot->AddToDatabase(); ... } void MyGameEntity::Destroy() { m_navBot->RemoveFromDatabase(); m_navBot = KY_NULL; } void MyGameEntity::UpdateLogic(KyFloat32 /*simulationStepsInSeconds*/) { if (m_navBot->GetFollowedPath() == KY_NULL && m_navBot->IsComputingPath() == false) // We need to compute a Path! { // Here we ask the Bot to launch a path computation. // We should test the return code of this function, but as this tutorial is pretty simple, we are sure it cannot fail: // - We correctly set the AStarQuery to the bot in the MyGameEntity::Initialize() function. // - We do not ask for a path computation while another path computation is in process. // Note that Kaim::Bot::ComputeAStarQueryAsync() will run the query during the Kaim::World::Update(). m_navBot->ComputeAStarQueryAsync(m_destinationPosition); } // If we are arrived... if (HasArrived()) { m_velocity = Kaim::Vec3f::Zero(); // Clear the followed path. m_navBot->ClearFollowedPath(); // Swap the positions. Kaim::Vec3f swap = m_destinationPosition; m_destinationPosition = m_startPosition; m_startPosition = swap; // Restart a move. m_navBot->ComputeAStarQueryAsync(m_destinationPosition); } } void MyGameEntity::UpdatePhysics(KyFloat32 simulationStepsInSeconds) { // Retrieve the velocity suggested m_velocity = m_navBot->GetBotOutput().m_outputVelocity; // Perform a simple integration clamping altitude on NavMesh m_position = m_navBot->ComputeMoveOnNavMesh(m_velocity, simulationStepsInSeconds); // Inform our Bot that the entity has moved // Note that NavMesh spatialization will be effective after next World::Update() call m_navBot->SetPositionAndVelocityAndFrontDirection(m_position, simulationStepsInSeconds); } bool MyGameEntity::HasArrived() { KyFloat32 arrivalPrecisionRadius = m_navBot->GetConfig().m_pathProgressConfig.m_checkPointRadius; if (m_navBot->HasReachedPosition(m_destinationPosition, arrivalPrecisionRadius)) { return true; } return false; } class MyGameLevel { public: ... void UpdateLogic(float deltaTimeInSeconds); void UpdatePhysics(float deltaTimeInSeconds); ... protected: ... MyGameEntity m_entity; }; bool MyGameLevel::Initialize(Kaim::World* world) { ... Do all other initializations // Initialize my entity m_entity.Initialize(world, Kaim::Vec3f(-11.5963f,1.06987f,10.4563f), Kaim::Vec3f(-16.636f,-26.7078f,8.10107f)); ... return true; } ... void MyGameLevel::UpdateLogic(float deltaTimeInSeconds) { ... m_entity.UpdateLogic(deltaTimeInSeconds); } void MyGameLevel::UpdatePhysics(float deltaTimeInSeconds) { m_entity.UpdatePhysics(deltaTimeInSeconds); } void MyGameLevel::Destroy() { // Destroy my entity. m_entity.Destroy(); ... Do all other destructions and releases. }
The code above mostly uses the default behavior of the path following system. However, the path following system is highly configurable; you can tailor it to the needs of your game by customizing it in various ways, responding to events that happen as the character follows the path, and even by writing your own implementations of the classes involved if necessary. For details, see Path Finding and Path Following.
Connect the Navigation Lab to your game, and you should see a yellow cylinder representing your character, moving back and forth between the start and end points set up for the AstarQuery. For example:
Note that: