遊戲AI(三)—行爲樹優化之基於事件的行爲樹

上一篇咱們講到了關於行爲樹的內存優化,這一篇咱們將講述行爲樹的另外一種優化方法——基於事件的行爲樹。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

相關文章
相關標籤/搜索