摘要:上一篇我們講到了關于行為樹的內存優化,這一篇我們將講述行為樹的另一種優化方法基于事件的行為樹。而函數負責將行為壓入隊列首端,節點則負責設置行為執行狀態并顯示調用監察函數。
上一篇我們講到了關于行為樹的內存優化,這一篇我們將講述行為樹的另一種優化方法——基于事件的行為樹。
問題在之前的行為樹中,我們每幀都要從根節點開始遍歷行為樹,而目的僅僅是為了得到最近激活的節點,既然如此,為什么我們不多帶帶維護一個保存這些行為的列表,以方便快速訪問呢。我們可以把這個列表叫做調度器,用來保存已經激活的行為,并在必要時更新他們。
解決辦法我們不再每幀都從根節點去遍歷行為樹,而是維護一個調度器負責保存已激活的節點,當正在執行的行為終止時,由其父節點決定接下來的行為。
監察函數為了實現基于事件的驅動,我們必須要有一個監察函數,當行為終止時,我們通過執行監察函數通知父節點并讓父節點做出相應處理,這里我們通過C++標準庫中的std::funcion實現監察函數
using BehaviorObserver = std::function
調度器負責管理基于事件的行為樹的核心代碼,負責對所有需要更新的行為進行集中式管理,不允許復合行為自主管理和運行自己的子節點。。。這里我們將調度器整合進了BehvaiorTree類。當然也可以弄個多帶帶的類進行管理。
class BehaviorTree { public: BehaviorTree(Behavior* InRoot) :Root(InRoot) {} void Tick(); bool Step(); void Start(Behavior* Bh,BehaviorObserver* Observe); void Stop(Behavior* Bh,EStatus Result); private: //已激活行為列表 std::dequeBehaviors; Behavior* Root; }; void BehaviorTree::Tick() { //將更新結束標記插入任務列表 Behaviors.push_back(nullptr); while (Step()) { } } bool BehaviorTree :: Step() { Behavior* Current = Behaviors.front(); Behaviors.pop_front(); //如果遇到更新結束標記則停止 if (Current == nullptr) return false; //執行行為更新 Current->Tick(); //如果該任務被終止則執行監察函數 if (Current->IsTerminate() && Current->Observer) { Current->Observer(Current->GetStatus()); } //否則將其插入隊列等待下次tick處理 else { Behaviors.push_back(Current); } } void BehaviorTree::Start(Behavior* Bh, BehaviorObserver* Observe) { if (Observe) { Bh->Observer = *Observe; } Behaviors.push_front(Bh); } void BehaviorTree::Stop(Behavior* Bh, EStatus Result) { assert(Result != EStatus::Running); Bh->SetStatus(Result); if (Bh->Observer) { Bh->Observer(Result); } }
我們通過一個雙端隊列保存已激活行為,在更新時從首端去走哦偶行為,再將需要更新的行為壓入隊列尾端。當發現任務終止時,執行其監察函數。
而Start()函數負責將行為壓入隊列首端,Stop()節點則負責設置行為執行狀態并顯示調用監察函數。
大部分動作和條件代碼并不受事件驅動方式的影響。而復合節點則是受事件驅動影響最明顯的節點。復合節點不再自己更新和管理子節點,而是通過向調度器提出請求以更新子節點。這里我們以Sequence節點為例。
/順序器:依次執行所有節點直到其中一個失敗或者全部成功位置
class Sequence :public Composite { public: virtual std::string Name() override { return "Sequence"; } static Behavior* Create() { return new Sequence(); } void OnChildComplete(EStatus Status); protected: virtual void OnInitialize() override; protected: Behaviors::iterator CurrChild; BehaviorTree* m_pBehaviorTree; };
void Sequence::OnInitialize() { CurrChild = Children.begin(); BehaviorObserver observer = std::bind(&Sequence::OnChildComplete, this, std::placeholders::_1); Tree->Start(*CurrChild, &observer); } void Sequence::OnChildComplete(EStatus Status) { Behavior* child = *CurrChild; //當當前子節點執行失敗時,順序器失敗 if (child->IsFailuer()) { m_pBehaviorTree->Stop(this, EStatus::Failure); return; } assert(child->GetStatus() == EStatus::Success); //當前子節點執行成功時,判斷是否執行到數組尾部 if (++CurrChild == Children.end()) { Tree->Stop(this, EStatus::Success); } //調度下一個子節點 else { BehaviorObserver observer = std::bind(&Sequence::OnChildComplete, this, std::placeholders::_1); Tree->Start(*CurrChild, &observer); } }
因為現在各節點由調度器統一管理,所以Update函數不再需要。我們在OnIntialize()函數中設置需要更新的首個節點,并將OnChildComplete作為其監察函數。在OnchildComplete函數中實現后續子節點的更新。
總結通過基于事件的方式,我們可以在行為樹執行時節省大量的函數調用,對其性能無疑是一次巨大的提升。
github連接
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/19684.html
摘要:原文鏈接本文內容包含以下章節本書英文版這個章節主要討論了在游戲中經常用到的一些基礎的人工智能算法。行為樹是把的圖轉變成為一顆樹結構。根據當前游戲的環境狀態得到某一個行為的效用值。 作者:蘇博覽商業轉載請聯系騰訊WeTest獲得授權,非商業轉載請注明出處。原文鏈接:https://wetest.qq.com/lab/view/427.html 本文內容包含以下章節: Chapter 2 ...
摘要:另外,當并行器滿足條件提前退出時,所有正在執行的子行為也應該立即被終止,我們在函數中調用每個子節點的終止方法監視器監視器是并行器的應用之一,通過在行為運行過程中不斷檢查是否滿足某條件,如果不滿足則立刻退出。將條件放在并行器的尾部即可。 從上古卷軸中形形色色的人物,到NBA2K中揮灑汗水的球員,從使命召喚中詭計多端的敵人,到刺客信條中栩栩如生的人群。游戲AI幾乎存在于游戲中的每個角落,默...
摘要:從游戲界的角度來說人工智能技術的發展可以為游戲帶來什么改變和收益。使用人工智能技術可以給游戲帶來更多更好的內容,也可以減輕游戲開發的成本。 作者:蘇博覽,騰訊互動娛樂高級研究員商業轉載請聯系騰訊WeTest獲得授權,非商業轉載請注明出處。原文鏈接:https://wetest.qq.com/lab/view/412.html 本文內容包含以下章節: Chapter 1.3 Why Ga...
閱讀 917·2023-04-25 18:51
閱讀 1870·2021-09-09 11:39
閱讀 3283·2019-08-30 15:53
閱讀 2099·2019-08-30 13:03
閱讀 1310·2019-08-29 16:17
閱讀 582·2019-08-29 11:33
閱讀 1884·2019-08-26 14:00
閱讀 2124·2019-08-26 13:41