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 Gameware 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 Gameware 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:
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 Update(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::Update(KyFloat32 simulationStepsInSeconds) { if (m_navBot->GetFollowedPath() == KY_NULL && m_navBot->IsComputingNewPath() == 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: // - The AStarQuery is set from the default NavigationProfile. // - We do not ask for a path computation while another path computation is in process. // Note that Kaim::Bot::ComputeNewPathToDestination() will run the query during the Kaim::World::Update(). m_navBot->ComputeNewPathToDestination(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->ComputeNewPathToDestination(m_destinationPosition); } // Retrieve the velocity suggested by the path following system, if available. m_velocity = m_navBot->GetBotOutput().m_outputVelocity; // Perform a simple integration. m_position += m_velocity*simulationStepsInSeconds; // Inform our Bot that the entity has moved. // Note that the update will be effective after next World::Update() call m_navBot->SetPosition(m_position); m_navBot->SetVelocityAndFrontDirection(m_velocity); } 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 Update(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::Update(float deltaTimeInSeconds) { ... m_entity.Update(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: