上一篇咱們講到了關於行爲樹的內存優化,這一篇咱們將講述行爲樹的另外一種優化方法——基於事件的行爲樹。git
在以前的行爲樹中,咱們每幀都要從根節點開始遍歷行爲樹,而目的僅僅是爲了獲得最近激活的節點,既然如此,爲何咱們不單獨維護一個保存這些行爲的列表,以方便快速訪問呢。咱們能夠把這個列表叫作調度器,用來保存已經激活的行爲,並在必要時更新他們。github
咱們再也不每幀都從根節點去遍歷行爲樹,而是維護一個調度器負責保存已激活的節點,當正在執行的行爲終止時,由其父節點決定接下來的行爲。數組
爲了實現基於事件的驅動,咱們必需要有一個監察函數,當行爲終止時,咱們經過執行監察函數通知父節點並讓父節點作出相應處理,這裏咱們經過C++標準庫中的std::funcion實現監察函數
using BehaviorObserver = std::function<void(EStatus)>;ide
調度器負責管理基於事件的行爲樹的核心代碼,負責對全部須要更新的行爲進行集中式管理,不容許複合行爲自主管理和運行本身的子節點。。。這裏咱們將調度器整合進了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::deque<Behavior*> Behaviors; 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函數中實現後續子節點的更新。this
經過基於事件的方式,咱們能夠在行爲樹執行時節省大量的函數調用,對其性能無疑是一次巨大的提高。
github鏈接code