游戏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 ))\ ...
随机推荐
- JavaScript系列----正则表达式
1.正则表达式 1.1.正则表达式的类型 正则表达式在JavaScript中,提供了一种内置的构造函数--RegExp. 正则表达式有三种匹配模式: g: 表示全局模式,即模式应用于所有的字符串,而非 ...
- 你不得不看的Python机器学习工具
IEEE Spectrum排行榜第一,Skill UP排名第一的开发工具,Stack Overflow年度调查中程序员最感兴趣的选择,Stack Overflow 6月份访问量最多的编程语言..... ...
- org.springframework.web.filter.DelegatingFilterProxy的作用
一.类结构 DelegatingFilterProxy类继承GenericFilterBean,间接实现了Filter,故而该类属于一个过滤器.那么就会有实现Filter中init.doFilter. ...
- Node学习——开篇
前言:自从下决心转学前端以来,我的专业课java基本荒废了,所以对于后台开发的逻辑也已基本忘干净了.但是作为一名准前端程序猿,我认为还是有必要了解后端开发的,虽不必深入学习,但是能够了解项目从前端到后 ...
- Java注解Annotation详解
从JDK5开始,Java增加了Annotation(注解),Annotation是代码里的特殊标记,这些标记可以在编译.类加载.运行时被读取,并执行相应的处理.通过使用Annotation,开发人员可 ...
- 用原型代替PRD时,原型应该包含哪些内容
随着互联网节奏越来越快,传统的需求文档已经比较难适应市场的脚步,特别对于要求敏捷的团队来说,冗余而细致入微的需求文档已经成为包袱(这么长个文档领导也不会看呀).目前大多数团队更喜爱直接使用原型来代替需 ...
- 使用ztree展示树形菜单结构
官网:http://www.treejs.cn/v3/main.php#_zTreeInfo 一.功能简介 在权限系统中,实现给角色指定菜单权限的功能.主要包括以下几点: 读取全部菜单项,并以树形结构 ...
- react入门到进阶(一)
一.何为react Facebook在F8会议上首次提出这个概念,一套全新的框架就此诞生. React 不是一个完整的 MVC.MVVM 框架,其只负责 View 层 React 跟 Web Comp ...
- 数据库 SQL Server2012安装步骤详解
这篇文章主要给大家讲解一下数据库SQL Server2012的安装步骤(若有雷同纯属巧合,还望谅解!) 在正式安装之前,我们需要进行两个设置: ① 首先需要关闭Windows防火墙,若不关闭,后续的安 ...
- webpack构建项目
webpack构建项目 案例代码戳这里 ps:每个案例对应相应的demo,例如"案例1"对应"demo1" 一.webpack基本功能及简单案例 安装webpa ...