游戏AI(三)—行为树优化之基于事件的行为树
上一篇我们讲到了关于行为树的内存优化,这一篇我们将讲述行为树的另一种优化方法——基于事件的行为树。
问题
在之前的行为树中,我们每帧都要从根节点开始遍历行为树,而目的仅仅是为了得到最近激活的节点,既然如此,为什么我们不单独维护一个保存这些行为的列表,以方便快速访问呢。我们可以把这个列表叫做调度器,用来保存已经激活的行为,并在必要时更新他们。
解决办法
我们不再每帧都从根节点去遍历行为树,而是维护一个调度器负责保存已激活的节点,当正在执行的行为终止时,由其父节点决定接下来的行为。
监察函数
为了实现基于事件的驱动,我们必须要有一个监察函数,当行为终止时,我们通过执行监察函数通知父节点并让父节点做出相应处理,这里我们通过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(三)—行为树优化之基于事件的行为树的更多相关文章
- DS线段树优化最短路&&01bfs浅谈
1简介 为什么需要?原因很简单,当需要有大量的边去连时,用线段树优化可以直接用点连向区间,或从区间连向点,或从区间连向区间,如果普通连边,复杂度是不可比拟的.下面简单讲解一下线段树(ST)优化建图. ...
- Libre OJ 2255 (线段树优化建图+Tarjan缩点+DP)
题面 传送门 分析 主体思路:若x能引爆y,从x向y连一条有向边,最后的答案就是从x出发能够到达的点的个数 首先我们发现一个炸弹可以波及到的范围一定是坐标轴上的一段连续区间 我们可以用二分查找求出炸弹 ...
- P3588 【[POI2015]PUS】(线段树优化建边)
P3588 [[POI2015]PUS] 终于有个能让我一遍过的题了,写篇题解纪念一下 给定长度为n的序列和其中部分已知的数,还有m个大小关系:区间\([l,r]\)中,有k个给定的数比剩下的\(r- ...
- 做游戏长知识------基于行为树与状态机的游戏AI(一)
孙广东 2014.6.30 AI. 我们的第一印象可能是机器人,如今主要说在游戏中的应用. 现代的计算机游戏中已经大量融入了AI元素,平时我们进行游戏时产生的交互都是由AI来完毕的.比方在RPG游戏中 ...
- Unity教程之-基于行为树与状态机的游戏AI
AI.我们的第一印象可能是机器人,现在主要说在游戏中的应用.关于AI的相关文章我们在前面也提到过,详细请戳这现代的计算机游戏中已经大量融入了AI元素,平时我们进行游戏时产生的交互都是由AI来完成的.比 ...
- 游戏AI(二)—行为树优化之
上一篇我们讲到了AI架构之一的行为树,本篇文章和下一篇文章我们将对行为树进行优化,在本篇文章中我们讲到的是内存优化 问题 上一篇中我们设计的行为树由于直接采用new进行动态内存分配,没有自己进行管理. ...
- 使用行为树(Behavior Tree)实现游戏AI
——————————————————————— 谈到游戏AI,很明显智能体拥有的知识条目越多,便显得更智能,但维护庞大数量的知识条目是个噩梦:使用有限状态机(FSM),分层有限状态机(HFSM),决策 ...
- 游戏AI之决策结构—有限状态机/行为树(2)
目录 有限状态机 行为树 控制节点 条件节点 行为节点 装饰节点 总结 额外/细节/优化 游戏AI的决策部分是比较重要的部分,游戏程序的老前辈们留下了两种经过考验的用于AI决策的结构: 有限状态机 行 ...
- HDU多校第三场 Hdu6606 Distribution of books 线段树优化DP
Hdu6606 Distribution of books 题意 把一段连续的数字分成k段,不能有空段且段和段之间不能有间隔,但是可以舍去一部分后缀数字,求\(min(max((\sum ai ))\ ...
随机推荐
- 在foreach的判断条件里执行方法会有效率问题吗?
楼猪平时一有空就有看别人代码的习惯,从许多优秀规范的代码中学习到了很多简约高效的写法和画龙点睛的思想精华.但是有的时候也会觉得某些写法很值得玩味.比如刚看到一段代码,在foreach的条件判断里加了一 ...
- Python 之 hello world
写好的内容不小心关机都没了...奈何..重写一遍吧... 本机环境 : windows7 sp1 64位 企业版,python3.6 一:安装与配置 1:首先大胆的下载python,新入门的建议下载3 ...
- 解决mssql localdb 中文乱码问题
使用以下查询语句即可. alter database "E:\.Net Core\Database\hm.mdf" set single_user with rollback im ...
- java 压缩导出多个excel
简单介绍下我实现的功能,首先是我的excel上传,它是以blob字段存储在oracel数据库中的,我实现的是循环遍历blob字段并使用io流进行打包下载,如有需要可自行修改 使用技术有,springM ...
- python 开源全文检索工具 Whoosh
About Whoosh Whoosh is a fast, featureful full-text indexing and searching library implemented in pu ...
- C# winform 实现图片轮播
作为一个C#winform刚刚入门的我 觉得这可能是初学者都会遇到的 我自己也觉得很神奇 首先 窗体里有一个Button按钮,和一个pictureBox 图片框 定义一个线程方法 /// < ...
- CSS实现模糊效果
HTML代码如下: <body> <h1>body设置了模糊效果</h1> <div id="aa"></div> &l ...
- 方法的形参、ref参数、out参数的区别
我们在定义方法时,经常会涉及到传参.因为引用类型的数据在用变量存储时,是存储的地址,所以在传参时,依然是传递的地址,但是值类型的数据在传参时就会有所不同.值类型数据在调用方法传参时,普通情况下是值传递 ...
- SpringMVC中遇到页面跳转出现404错误的问题
今天遇到了一个问题: 使用SpringMVC时,出现页面无法跳转的情况(404错误), 出现这个异常的原因在于SpringMVC的配置文件中控制器的配置书写错误: 原代码: <context:c ...
- mysql安装后服务启动不了(总结)
mysql安装后服务启动不了 1.1 前言 最近真的是倒霉到家,装个mysql都能把所有的问题给问候了一遍······不过这也是一个宝贵的经验,得好好总结下,毕竟也不知道以后会不会再次遇到.如果有网友 ...