C++打AI(三)—行为培训优化的因事件之行事培训

高达一样篇我们讲到了关于行为树的内存优化,这等同篇我们用讲述行为树的其余一样种优化措施——基于事件之作为培训。

问题

于前头的所作所为树中,我们每帧都如起根节点开始遍历行为培训,而目的无非是为了拿走最近激活的节点,既然如此,为什么我们无独立维护一个封存这些作为之列表,以造福快速访问呢。我们好将这个列表叫做调度器,用来保存已经激活的所作所为,并以必要时更新他们。

解决办法

咱们不再每帧都由根节点去遍历行为培训,而是维护一个调度器负责保存已激活的节点,当在实行之所作所为已时,由该父节点决定接下去的一言一行。

监察函数

以实现冲事件之使,我们要要出一个监察函数,当行已时,我们由此实践监察函数通知父节点并被父节点做出相应处理,这里我们经过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::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函数中落实后续子节点的更新。

总结

由此根据事件的法门,我们可以以表现培训执行时省去大量的函数调用,对那属性可靠是一律不良伟大的升级换代。
github连接