上一篇我们讲到了关于行为树的内存优化,这一篇我们将讲述行为树的另一种优化方法——基于事件的行为树。

问题

在之前的行为树中,我们每帧都要从根节点开始遍历行为树,而目的仅仅是为了得到最近激活的节点,既然如此,为什么我们不单独维护一个保存这些行为的列表,以方便快速访问呢。我们可以把这个列表叫做调度器,用来保存已经激活的行为,并在必要时更新他们。

解决办法

我们不再每帧都从根节点去遍历行为树,而是维护一个调度器负责保存已激活的节点,当正在执行的行为终止时,由其父节点决定接下来的行为。

监察函数

为了实现基于事件的驱动,我们必须要有一个监察函数,当行为终止时,我们通过执行监察函数通知父节点并让父节点做出相应处理,这里我们通过C++标准库中的std::funcion实现监察函数

using BehaviorObserver = std::function<void(EStatus)>;

行为调度器

调度器负责管理基于事件的行为树的核心代码,负责对所有需要更新的行为进行集中式管理,不允许复合行为自主管理和运行自己的子节点。。。这里我们将调度器整合进了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连接

游戏AI(三)—行为树优化之基于事件的行为树的更多相关文章

  1. DS线段树优化最短路&&01bfs浅谈

    1简介 为什么需要?原因很简单,当需要有大量的边去连时,用线段树优化可以直接用点连向区间,或从区间连向点,或从区间连向区间,如果普通连边,复杂度是不可比拟的.下面简单讲解一下线段树(ST)优化建图. ...

  2. Libre OJ 2255 (线段树优化建图+Tarjan缩点+DP)

    题面 传送门 分析 主体思路:若x能引爆y,从x向y连一条有向边,最后的答案就是从x出发能够到达的点的个数 首先我们发现一个炸弹可以波及到的范围一定是坐标轴上的一段连续区间 我们可以用二分查找求出炸弹 ...

  3. P3588 【[POI2015]PUS】(线段树优化建边)

    P3588 [[POI2015]PUS] 终于有个能让我一遍过的题了,写篇题解纪念一下 给定长度为n的序列和其中部分已知的数,还有m个大小关系:区间\([l,r]\)中,有k个给定的数比剩下的\(r- ...

  4. 做游戏长知识------基于行为树与状态机的游戏AI(一)

    孙广东 2014.6.30 AI. 我们的第一印象可能是机器人,如今主要说在游戏中的应用. 现代的计算机游戏中已经大量融入了AI元素,平时我们进行游戏时产生的交互都是由AI来完毕的.比方在RPG游戏中 ...

  5. Unity教程之-基于行为树与状态机的游戏AI

    AI.我们的第一印象可能是机器人,现在主要说在游戏中的应用.关于AI的相关文章我们在前面也提到过,详细请戳这现代的计算机游戏中已经大量融入了AI元素,平时我们进行游戏时产生的交互都是由AI来完成的.比 ...

  6. 游戏AI(二)—行为树优化之

    上一篇我们讲到了AI架构之一的行为树,本篇文章和下一篇文章我们将对行为树进行优化,在本篇文章中我们讲到的是内存优化 问题 上一篇中我们设计的行为树由于直接采用new进行动态内存分配,没有自己进行管理. ...

  7. 使用行为树(Behavior Tree)实现游戏AI

    ——————————————————————— 谈到游戏AI,很明显智能体拥有的知识条目越多,便显得更智能,但维护庞大数量的知识条目是个噩梦:使用有限状态机(FSM),分层有限状态机(HFSM),决策 ...

  8. 游戏AI之决策结构—有限状态机/行为树(2)

    目录 有限状态机 行为树 控制节点 条件节点 行为节点 装饰节点 总结 额外/细节/优化 游戏AI的决策部分是比较重要的部分,游戏程序的老前辈们留下了两种经过考验的用于AI决策的结构: 有限状态机 行 ...

  9. HDU多校第三场 Hdu6606 Distribution of books 线段树优化DP

    Hdu6606 Distribution of books 题意 把一段连续的数字分成k段,不能有空段且段和段之间不能有间隔,但是可以舍去一部分后缀数字,求\(min(max((\sum ai ))\ ...

随机推荐

  1. Spring各jar包作用及依赖

    先附spring各版本jar包下载链接http://repo.spring.io/release/org/springframework/spring/ spring.jar 是包含有完整发布模块的单 ...

  2. 【OpenCV】一种基于阈值的图片中的文字分割

    在今年泰迪杯A题电商中图片的文字识别这道题中,我们先用了一种很笨的办法来分割字符. 首先对图片进行灰度化,然后二值化,这里的二值化要选择一个合适的阈值.然后我们进行轮廓的提取,计算轮廓最小矩形的面积, ...

  3. 学python+django去北京找工作,靠谱吗?

    有些朋友说,自己的学习能力还可以.倾向于python加框架,如django,python本来就会一些.不太了解北京公司的情况,想知道现学的python+django在北京找到工作有多少可能性. 要想知 ...

  4. [转载] 十五分钟介绍 Redis数据结构

    转载自http://blog.nosqlfan.com/html/3202.html?ref=rediszt Redis是一种面向“键/值”对类型数据的分布式NoSQL数据库系统,特点是高性能,持久存 ...

  5. PHP常用的函数与小技巧

    密码加密与验证 password_hash - 创建密码的哈希(hash) string password_hash ( string $password , integer $algo [, arr ...

  6. Instrumentation 框架简介

    原文地址:http://www.cnblogs.com/xirihanlin/archive/2010/06/15/1758677.html Android提供了一系列强大的测试工具,它针对Andro ...

  7. 手 Q 人脸识别动画实现详解

    欢迎大家前往腾讯云社区,获取更多腾讯海量技术实践干货哦~ 前言 开门见山,先来看下效果吧. 看到这么酷炫的效果图,不得不赞叹一下我们的设计师.然而,站在程序员的角度上看,除了酷炫之外更多的是复杂.但是 ...

  8. 泛型里的super和extend

    <? extends T>和<? super T>应该怎么用? 网上看到一些比较难懂的回答,但是在EffectiveJava(2th Edition)遇到简单明了的解释: If ...

  9. JavaEE中的MVC(四)AOP代理

    咱们来吹牛,JDK的动态代理在AOP(Aspect Oriented Programming,面向切面编程)中被称为AOP代理,而AOP是Spring框架中的重要组成部分. 代理模式 但是什么是代理模 ...

  10. 2746:约瑟夫问题poj

    2746:约瑟夫问题 总时间限制:  1000ms 内存限制:  65536kB 描述 约瑟夫问题:有n只猴子,按顺时针方向围成一圈选大王(编号从1到n),从第1号开始报数,一直数到m,数到m的猴子退 ...