上一篇咱们讲到了关于行为树的内存优化,这一篇咱们将讲述行为树的另外一种优化方法——基于事件的行为树。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