diff --git a/_DEMO_DEBUG/GPP_Plugin_d.dll b/_DEMO_DEBUG/GPP_Plugin_d.dll index 0a66bd9..7c40a11 100644 Binary files a/_DEMO_DEBUG/GPP_Plugin_d.dll and b/_DEMO_DEBUG/GPP_Plugin_d.dll differ diff --git a/_DEMO_DEBUG/GPP_Plugin_d.pdb b/_DEMO_DEBUG/GPP_Plugin_d.pdb index 5feb62c..06ecf8b 100644 Binary files a/_DEMO_DEBUG/GPP_Plugin_d.pdb and b/_DEMO_DEBUG/GPP_Plugin_d.pdb differ diff --git a/_DEMO_DEBUG/imgui.ini b/_DEMO_DEBUG/imgui.ini index 100d1b0..bd31cb6 100644 --- a/_DEMO_DEBUG/imgui.ini +++ b/_DEMO_DEBUG/imgui.ini @@ -4,8 +4,8 @@ Size=400,400 Collapsed=0 [WORLD INFO [462 FPS]] -Pos=651,10 -Size=240,431 +Pos=710,10 +Size=240,520 Collapsed=0 [STATS] @@ -1708,3 +1708,58 @@ Pos=710,10 Size=240,520 Collapsed=0 +[WORLD INFO [820 FPS]] +Pos=710,10 +Size=240,520 +Collapsed=0 + +[WORLD INFO [638 FPS]] +Pos=710,10 +Size=240,520 +Collapsed=0 + +[WORLD INFO [597 FPS]] +Pos=710,10 +Size=240,520 +Collapsed=0 + +[WORLD INFO [562 FPS]] +Pos=710,10 +Size=240,520 +Collapsed=0 + +[WORLD INFO [530 FPS]] +Pos=710,10 +Size=240,520 +Collapsed=0 + +[WORLD INFO [503 FPS]] +Pos=710,10 +Size=240,520 +Collapsed=0 + +[WORLD INFO [443 FPS]] +Pos=710,10 +Size=240,520 +Collapsed=0 + +[WORLD INFO [397 FPS]] +Pos=710,10 +Size=240,520 +Collapsed=0 + +[WORLD INFO [376 FPS]] +Pos=710,10 +Size=240,520 +Collapsed=0 + +[WORLD INFO [358 FPS]] +Pos=710,10 +Size=240,520 +Collapsed=0 + +[WORLD INFO [325 FPS]] +Pos=710,10 +Size=240,520 +Collapsed=0 + diff --git a/project/Behaviour.cpp b/project/Behaviour.cpp new file mode 100644 index 0000000..0d2857c --- /dev/null +++ b/project/Behaviour.cpp @@ -0,0 +1,63 @@ +#include "stdafx.h" +#include "Behaviour.h" +#include "BehaviourTree.h" +#include + +namespace BT_Action +{ + BT::State FindAHouse(Blackboard* blackboardPtr) { + IExamInterface* interfacePtr{}; + blackboardPtr->GetData("Interface", interfacePtr); + + const Elite::Vector2 worldDimensions{ interfacePtr->World_GetInfo().Dimensions }; + + std::random_device rd; // obtain a random number from hardware + std::mt19937 seed(rd()); // seed the generator + std::uniform_int_distribution<> range(-worldDimensions.x, worldDimensions.x); // define the range + + const Elite::Vector2 randomLocation(range(seed), range(seed)); + const Elite::Vector2 target = interfacePtr->NavMesh_GetClosestPathPoint(randomLocation); + + if (randomLocation != target) { + blackboardPtr->ChangeData("Target", randomLocation); + + std::cout << "Data send " << randomLocation << "\n"; + + return BT::State::Success; + } + + return BT::State::Failure; + } + + BT::State GoToTarget(Blackboard* blackboardPtr) { + IExamInterface* interfacePtr{}; + Elite::Vector2 target{}; + SteeringPlugin_Output steering{}; + + blackboardPtr->GetData("Interface", interfacePtr); + blackboardPtr->GetData("Target", target); + blackboardPtr->GetData("Steering", steering); + + std::cout << "target received " << target << "\n"; + + const auto agentInfo = interfacePtr->Agent_GetInfo(); + + const auto nextTargetPos = interfacePtr->NavMesh_GetClosestPathPoint(target); + + steering.LinearVelocity = nextTargetPos - agentInfo.Position; + steering.LinearVelocity.Normalize(); + steering.LinearVelocity *= agentInfo.MaxLinearSpeed; + + if (Distance(nextTargetPos, agentInfo.Position) < 2.f) { + steering.LinearVelocity = Elite::ZeroVector2; + + std::cout << "target reached at " << target << "\n"; + + return BT::State::Success; + } + + blackboardPtr->ChangeData("Steering", steering); + + return BT::State::Running; + } +} \ No newline at end of file diff --git a/project/Behaviour.h b/project/Behaviour.h new file mode 100644 index 0000000..4c28968 --- /dev/null +++ b/project/Behaviour.h @@ -0,0 +1,18 @@ +#pragma once + +namespace BT +{ + enum class State; +} + +class Blackboard; + +namespace BT_Action +{ + BT::State FindAHouse(Blackboard* blackboardPtr); + BT::State GoToTarget(Blackboard* blackboardPtr); +} + +namespace BT_Conditions +{ +} diff --git a/project/BehaviourTree.cpp b/project/BehaviourTree.cpp index 129c4af..36adf19 100644 --- a/project/BehaviourTree.cpp +++ b/project/BehaviourTree.cpp @@ -1,7 +1,133 @@ -#pragma once +#include "stdafx.h" +#include "BehaviourTree.h" +#include "SurvivalAgentPlugin.h" -class BehaviorTree -{ -public: +using namespace BT; -}; +#pragma region COMPOSITES +Composite::Composite(const std::vector& childBehaviors) { + m_ChildBehaviors = childBehaviors; +} + +Composite::~Composite() { + for (auto pb : m_ChildBehaviors) + SAFE_DELETE(pb) + m_ChildBehaviors.clear(); +} + +//SELECTOR +State Selector::Execute(Blackboard* blackboardPtr) { + // Loop over all children in m_ChildBehaviors + for (IBehavior* pBeh : m_ChildBehaviors) { + //Every Child: Execute and store the result in m_CurrentState + m_CurrentState = pBeh->Execute(blackboardPtr); + + //Check the currentstate and apply the selector Logic: + //if a child returns Success: + if (m_CurrentState == State::Success) { + //stop looping over all children and return Success + return m_CurrentState; + } + + //if a child returns Running: + if (m_CurrentState == State::Running) { + //Running: stop looping and return Running + return m_CurrentState; + } + //The selector fails if all children failed. + } + + //All children failed + m_CurrentState = State::Failure; + return m_CurrentState; +} +//SEQUENCE +State Sequence::Execute(Blackboard* blackboardPtr) { + //Loop over all children in m_ChildBehaviors + for (IBehavior* pBeh : m_ChildBehaviors) { + //Every Child: Execute and store the result in m_CurrentState + m_CurrentState = pBeh->Execute(blackboardPtr); + + //Check the currentstate and apply the sequence Logic: + //if a child returns Failed: + if (m_CurrentState == State::Failure) { + //stop looping over all children and return Failed + return m_CurrentState; + } + + //if a child returns Running: + if (m_CurrentState == State::Running) { + //Running: stop looping and return Running + return m_CurrentState; + + } + + //The selector succeeds if all children succeeded. + } + + //All children succeeded + m_CurrentState = State::Success; + return m_CurrentState; +} + +State PartialSequence::Execute(Blackboard* blackboardPtr) { + while (m_CurrentBehaviorIndex < m_ChildBehaviors.size()) { + m_CurrentState = m_ChildBehaviors[m_CurrentBehaviorIndex]->Execute(blackboardPtr); + switch (m_CurrentState) { + case State::Failure: + m_CurrentBehaviorIndex = 0; + return m_CurrentState; + case State::Success: + ++m_CurrentBehaviorIndex; + m_CurrentState = State::Running; + return m_CurrentState; + case State::Running: + return m_CurrentState; + } + } + + m_CurrentBehaviorIndex = 0; + m_CurrentState = State::Success; + return m_CurrentState; +} +#pragma endregion + + +State Conditional::Execute(Blackboard* blackboardPtr) { + if (m_ConditionalPtr == nullptr) + return State::Failure; + + if (m_ConditionalPtr(blackboardPtr)) { + m_CurrentState = State::Success; + } + else { + m_CurrentState = State::Failure; + } + return m_CurrentState; +} + +State Action::Execute(Blackboard* blackboardPtr) { + if (m_ActionPtr == nullptr) + return State::Failure; + + m_CurrentState = m_ActionPtr(blackboardPtr); + return m_CurrentState; +} + +BehaviorTree::~BehaviorTree() { + SAFE_DELETE(m_RootBehaviorPtr) + SAFE_DELETE(m_BlackboardPtr) //Takes ownership of passed blackboard! +} + +void BehaviorTree::Update() { + if (m_RootBehaviorPtr == nullptr) { + m_CurrentState = State::Failure; + return; + } + + m_CurrentState = m_RootBehaviorPtr->Execute(m_BlackboardPtr); +} + +Blackboard* BehaviorTree::GetBlackboard() const { + return m_BlackboardPtr; +} \ No newline at end of file diff --git a/project/BehaviourTree.h b/project/BehaviourTree.h index e1fb80f..a6c9e48 100644 --- a/project/BehaviourTree.h +++ b/project/BehaviourTree.h @@ -1,2 +1,115 @@ -#include "stdafx.h" -#include "BehaviorTree.h" \ No newline at end of file +#include + +#include "Blackboard.h" +namespace BT +{ + enum class State + { + Failure, + Success, + Running + }; + + class IBehavior + { + public: + IBehavior() = default; + virtual ~IBehavior() = default; + virtual State Execute(Blackboard* blackboardPtr) = 0; + + protected: + State m_CurrentState = State::Failure; + }; + +#pragma region COMPOSITES + class Composite : public IBehavior + { + public: + explicit Composite(const std::vector& childBehaviors); + ~Composite() override; + + State Execute(Blackboard* blackboardPtr) override = 0; + + protected: + std::vector m_ChildBehaviors = {}; + }; + + class Selector final : public Composite + { + public: + explicit Selector(std::vector childBehaviors) : + Composite(std::move(childBehaviors)) { + } + ~Selector() override = default; + + State Execute(Blackboard* blackboardPtr) override; + }; + + class Sequence : public Composite + { + public: + inline explicit Sequence(std::vector childBehaviors) : + Composite(std::move(childBehaviors)) { + } + ~Sequence() override = default; + + State Execute(Blackboard* blackboardPtr) override; + }; + + class PartialSequence final : public Sequence + { + public: + inline explicit PartialSequence(std::vector childBehaviors) + : Sequence(std::move(childBehaviors)) { + } + ~PartialSequence() override = default; + + State Execute(Blackboard* blackboardPtr) override; + + private: + unsigned int m_CurrentBehaviorIndex = 0; + }; +#pragma endregion + + class Conditional final : public IBehavior + { + public: + explicit Conditional(std::function fp) + : m_ConditionalPtr(std::move(fp)) { + } + + State Execute(Blackboard* blackboardPtr) override; + + private: + std::function m_ConditionalPtr = nullptr; + }; + + class Action final : public IBehavior + { + public: + explicit Action(std::function fp) : m_ActionPtr(std::move(fp)) {} + State Execute(Blackboard* blackboardPtr) override; + + private: + std::function m_ActionPtr = nullptr; + }; + + class BehaviorTree final + { + public: + inline explicit BehaviorTree(Blackboard* blackboardPtr, IBehavior* pRootBehavior) + : m_BlackboardPtr(blackboardPtr), + m_RootBehaviorPtr(pRootBehavior) { + } + ~BehaviorTree(); + + void Update(); + + Blackboard* GetBlackboard() const; + + private: + State m_CurrentState = State::Failure; + Blackboard* m_BlackboardPtr = nullptr; + IBehavior* m_RootBehaviorPtr = nullptr; + }; +} \ No newline at end of file diff --git a/project/BlackBoard.h b/project/BlackBoard.h index db8fb2f..e7680be 100644 --- a/project/BlackBoard.h +++ b/project/BlackBoard.h @@ -3,17 +3,15 @@ #include #include - -class IBlackBoardField +class IBlackboardField { public: - IBlackBoardField() = default; - virtual ~IBlackBoardField() = default; + virtual ~IBlackboardField() = default; }; //BlackboardField does not take ownership of pointers whatsoever! template -class BlackboardField : public IBlackBoardField +class BlackboardField final : public IBlackboardField { public: explicit BlackboardField(T data) : m_Data(data) { @@ -30,7 +28,7 @@ class Blackboard final public: Blackboard() = default; ~Blackboard() { - for (auto el : m_BlackboardData) + for (const auto& el : m_BlackboardData) delete el.second; m_BlackboardData.clear(); } @@ -40,6 +38,7 @@ public: Blackboard(Blackboard&& other) = delete; Blackboard& operator=(Blackboard&& other) = delete; + //Add data to the blackboard template bool AddData(const std::string& name, T data) { auto it = m_BlackboardData.find(name); if (it == m_BlackboardData.end()) { @@ -50,6 +49,7 @@ public: return false; } + //Change the data of the blackboard template bool ChangeData(const std::string& name, T data) { auto it = m_BlackboardData.find(name); if (it != m_BlackboardData.end()) { @@ -63,6 +63,7 @@ public: return false; } + //Get the data from the blackboard template bool GetData(const std::string& name, T& data) { BlackboardField* p = dynamic_cast*>(m_BlackboardData[name]); @@ -75,6 +76,5 @@ public: } private: - std::unordered_map m_BlackboardData; + std::unordered_map m_BlackboardData; }; - diff --git a/project/CMakeLists.txt b/project/CMakeLists.txt index 0abddc8..301d8ce 100644 --- a/project/CMakeLists.txt +++ b/project/CMakeLists.txt @@ -3,7 +3,7 @@ # ADD NEW .cpp FILES HERE add_library(Exam_Plugin SHARED "stdafx.cpp" - "SurvivalAgentPlugin.cpp" "BehaviourTree.cpp" "BehaviourTree.h" "BlackBoard.h" "Thinker.h" "Thinker.cpp") + "SurvivalAgentPlugin.cpp" "BehaviourTree.cpp" "BehaviourTree.h" "BlackBoard.h" "Thinker.h" "Thinker.cpp" "Behaviour.h" "Behaviour.cpp") target_link_libraries(Exam_Plugin PUBLIC ${EXAM_LIB_DEBUG}) target_include_directories(Exam_Plugin PUBLIC ${EXAM_INCLUDE_DIR}) diff --git a/project/SurvivalAgentPlugin.cpp b/project/SurvivalAgentPlugin.cpp index 08c1bba..90817f0 100644 --- a/project/SurvivalAgentPlugin.cpp +++ b/project/SurvivalAgentPlugin.cpp @@ -1,8 +1,9 @@ #include "stdafx.h" #include "SurvivalAgentPlugin.h" #include "IExamInterface.h" - -using namespace std; +#include "Blackboard.h" +#include "BehaviourTree.h" +#include "Behaviour.h" //Called only once, during initialization void SurvivalAgentPlugin::Initialize(IBaseInterface* pInterface, PluginInfo& info) @@ -16,6 +17,35 @@ void SurvivalAgentPlugin::Initialize(IBaseInterface* pInterface, PluginInfo& inf info.Student_Name = "Bram Verhulst";//No special characters allowed. Highscores won't work with special characters. info.Student_Class = "2DAE11"; info.LB_Password = "ILikeCuteCats!";//Don't use a real password! This is only to prevent other students from overwriting your highscore! + + Blackboard* blackboard = CreateBlackboard(); + m_BehaviourTree = new BT::BehaviorTree(blackboard, + new BT::Selector({ + new BT::PartialSequence({ + new BT::Action(BT_Action::FindAHouse), + new BT::Action(BT_Action::GoToTarget) + }) + }) + ); +} + +Blackboard* SurvivalAgentPlugin::CreateBlackboard() { + Blackboard* blackboard = new Blackboard(); + blackboard->AddData("Interface", m_pInterface); + blackboard->AddData("Steering", SteeringPlugin_Output{}); + blackboard->AddData("Target", m_Target); + return blackboard; +} + +void SurvivalAgentPlugin::UpdateBlackboard(const SteeringPlugin_Output& steering) { + Blackboard* blackboard{ m_BehaviourTree->GetBlackboard() }; + + //blackboard->ChangeData("playerPos", m_pInterface->Agent_GetInfo().Position); + blackboard->ChangeData("Steering", steering); +} + +SurvivalAgentPlugin::~SurvivalAgentPlugin() { + SAFE_DELETE(m_BehaviourTree); } //Called only once @@ -125,101 +155,15 @@ SteeringPlugin_Output SurvivalAgentPlugin::UpdateSteering(float dt) //Use the Interface (IAssignmentInterface) to 'interface' with the AI_Framework auto agentInfo = m_pInterface->Agent_GetInfo(); + UpdateBlackboard(steering); - //Use the navmesh to calculate the next navmesh point - //auto nextTargetPos = m_pInterface->NavMesh_GetClosestPathPoint(checkpointLocation); + m_BehaviourTree->Update(); - //OR, Use the mouse target - auto nextTargetPos = m_pInterface->NavMesh_GetClosestPathPoint(m_Target); + m_BehaviourTree->GetBlackboard()->GetData("Steering", steering); - //FOV USAGE DEMO - //=============== + steering.AngularVelocity = m_AngSpeed; + steering.AutoOrient = false; - //FOV stats = CHEAP! info about the FOV - FOVStats stats = m_pInterface->FOV_GetStats(); - - //FOV data (snapshot of the FOV of the current frame) = EXPENSIVE! returns a new vector for every call - auto vHousesInFOV = m_pInterface->GetHousesInFOV(); - auto vEnemiesInFOV = m_pInterface->GetEnemiesInFOV(); - auto vItemsInFOV = m_pInterface->GetItemsInFOV(); - auto vPurgezonesInFOV = m_pInterface->GetPurgeZonesInFOV(); - - //for (auto& zoneInfo : vPurgezonesInFOV) - //{ - // std::cout << "Purge Zone in FOV:" << zoneInfo.Center.x << ", "<< zoneInfo.Center.y << "---Radius: "<< zoneInfo.Radius << std::endl; - //} - - //for (auto& enemyInfo : vEnemiesInFOV) - //{ - // std::cout << "Enemy in FOV:" << enemyInfo.Location.x << ", " << enemyInfo.Location.y << "---Health: " << enemyInfo.Health << std::endl; - //} - - //for (auto& item : vItemsInFOV) - //{ - // std::cout << "Item in FOV:" << item.Location.x << ", " << item.Location.y << "---Value: " << item.Value << std::endl; - //} - - //INVENTORY USAGE DEMO - //******************** - - if (m_GrabItem) - { - ItemInfo item; - //Item_Grab > When DebugParams.AutoGrabClosestItem is TRUE, the Item_Grab function returns the closest item in range - //Keep in mind that DebugParams are only used for debugging purposes, by default this flag is FALSE - //Otherwise, use GetEntitiesInFOV() to retrieve a vector of all entities in the FOV (EntityInfo) - //Item_Grab gives you the ItemInfo back, based on the passed EntityHash (retrieved by GetEntitiesInFOV) - if (m_pInterface->GrabNearestItem(item)) - { - //Once grabbed, you can add it to a specific inventory slot - //Slot must be empty - m_pInterface->Inventory_AddItem(m_InventorySlot, item); - } - } - - if (m_UseItem) - { - //Use an item (make sure there is an item at the given inventory slot) - m_pInterface->Inventory_UseItem(m_InventorySlot); - } - - if (m_RemoveItem) - { - //Remove an item from a inventory slot - m_pInterface->Inventory_RemoveItem(m_InventorySlot); - } - - if (m_DestroyItemsInFOV) - { - for (auto& item : vItemsInFOV) - { - m_pInterface->DestroyItem(item); - } - } - - //Simple Seek Behaviour (towards Target) - steering.LinearVelocity = nextTargetPos - agentInfo.Position; //Desired Velocity - steering.LinearVelocity.Normalize(); //Normalize Desired Velocity - steering.LinearVelocity *= agentInfo.MaxLinearSpeed; //Rescale to Max Speed - - if (Distance(nextTargetPos, agentInfo.Position) < 2.f) - { - steering.LinearVelocity = Elite::ZeroVector2; - } - - //steering.AngularVelocity = m_AngSpeed; //Rotate your character to inspect the world while walking - - steering.AutoOrient = true; //Setting AutoOrient to true overrides the AngularVelocity - steering.RunMode = m_CanRun; //If RunMode is True > MaxLinearSpeed is increased for a limited time (until your stamina runs out) - - //SteeringPlugin_Output is works the exact same way a SteeringBehaviour output - - //@End (Demo Purposes) - - m_GrabItem = false; //Reset State - m_UseItem = false; - m_RemoveItem = false; - m_DestroyItemsInFOV = false; return steering; } diff --git a/project/SurvivalAgentPlugin.h b/project/SurvivalAgentPlugin.h index 43dc647..124de63 100644 --- a/project/SurvivalAgentPlugin.h +++ b/project/SurvivalAgentPlugin.h @@ -2,14 +2,19 @@ #include "IExamPlugin.h" #include "Exam_HelperStructs.h" +namespace BT{ + class BehaviorTree; +} + class IBaseInterface; class IExamInterface; +class Blackboard; -class SurvivalAgentPlugin :public IExamPlugin +class SurvivalAgentPlugin final :public IExamPlugin { public: - SurvivalAgentPlugin() {}; - virtual ~SurvivalAgentPlugin() {}; + SurvivalAgentPlugin() = default; + virtual ~SurvivalAgentPlugin() override; void Initialize(IBaseInterface* pInterface, PluginInfo& info) override; void DllInit() override; @@ -34,6 +39,10 @@ private: float m_AngSpeed = 0.f; //Demo purpose UINT m_InventorySlot = 0; + + Blackboard* CreateBlackboard(); + void UpdateBlackboard(const SteeringPlugin_Output& steering); + BT::BehaviorTree* m_BehaviourTree = nullptr; }; //ENTRY @@ -41,7 +50,7 @@ private: //The plugin returned by this function is also the plugin used by the host program extern "C" { - __declspec (dllexport) IPluginBase* Register() + inline __declspec (dllexport) IPluginBase* Register() { return new SurvivalAgentPlugin(); }