游戏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连接

时间: 2024-10-09 13:08:50

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

[Poi2010]Monotonicity 2 (线段树优化DP)

题目描述 给出N个正整数a[1..N],再给出K个关系符号(>.<或=)s[1..k].选出一个长度为L的子序列(不要求连续),要求这个子序列的第i项和第i+1项的的大小关系为s[(i-1)mod K+1].求出L的最大值. 输入 第一行两个正整数,分别表示N和K (N, K <= 500,000).第二行给出N个正整数,第i个正整数表示a[i] (a[i] <= 10^6).第三行给出K个空格隔开关系符号(>.<或=),第i个表示s[i]. 输出 一个正整数,表示L的

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

上一篇我们讲到了AI架构之一的行为树,本篇文章和下一篇文章我们将对行为树进行优化,在本篇文章中我们讲到的是内存优化 问题 上一篇中我们设计的行为树由于直接采用new进行动态内存分配,没有自己进行管理.因此行为树各节点的存储位置会散布在内存空间的各处,行为树在不同节点中切换时会导致Cache频繁失效. 通过内存管理改变行为树节点的内存分布,可以显著提高行为树的内存性能. 解决办法 我们可以在BehaviorTree中引入一组内存分配的API来保证各节点尽量分配在连续的内存上,代码如下 Behavi

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

原地址:http://blog.csdn.net/akara/article/details/6084786 [原创]使用行为树(Behavior Tree)实现游戏AIby AKara 2010-12-09 @ http://blog.csdn.net/akara @ akarachen(at)gmail.com @weibo.com/akaras 谈到游戏AI,很明显智能体拥有的知识条目越多,便显得更智能,但维护庞大数量的知识条目是个噩梦:使用有限状态机(FSM),分层有限状态机(HFSM)

实现简易而强大的游戏AI——FSM,有限状态机

http://blog.friskit.me/2012/05/introduction-of-fsm/ 在很久很久以前,受限于计算机性能和图形效果,游戏往往是以玩家为唯一主动对象的,玩家发出动作,游戏响应结果.除此之外,不需要系统在玩家没有发出动作时产生响应.可以说,玩家的动作与游戏是“同步”的. 随着计算机的处理能力的发展,更绚丽的游戏逐渐产生.玩家就不能只满足盯着屏幕上静态的一张张图片进行游戏.也就是说,游戏应该有自己的方式能够与玩家主动沟通.这样才能使游戏更加生动,虚拟的环境显得更加真实.

棋牌源码搭建教程之棋牌游戏AI算法

棋牌游戏客户端实现采用Flash 9开发,服务端采用Win32+VC6开发(基于IOCP),数据库网关采用Win32+VC6开发(基于IOCP,MySQL5实现了处理线程池和数据库连接池).虽然服务器端去年就已经完成,但相应的机器人AI算法一直没有能力去实现.今天把它拿到Blog上来希望有机会和感兴趣的兄弟们探讨下. Kevin在他的Blog上给出了他的实现,其给出的算法思想是用宽度优先生成一棵搜索树,再根据玩牌的技巧进行剪枝与判权,机器人的AI能够像养成类游戏那样,实现在蹂躏下慢慢成长,水平逐

对弈类游戏的人工智能(5)--2048游戏AI的解读

前言: 闲得没事, 网上搜"游戏AI", 看到一篇<<2048游戏的最佳算法是?来看看AI版作者的回答>>的文章. 而这篇文章刚好和之前讲的对弈类游戏AI对应上. 于是有了想法, 想把它作为一个实例来进行解读, 从而对之前偏理论的文章做个总结. 承接上四篇博文: (1). 评估函数+博弈树算法 (2). 学习算法 (3). 博弈树优化 (4). 游戏AI的落地 可能有些人会疑惑? 2048并非对弈类类型? 传统的博弈树模型是否能应用于此? 客官莫急, 让我们来一

【bzoj4276】[ONTAK2015]Bajtman i Okr?g?y Robin 线段树优化建图+费用流

题目描述 有n个强盗,其中第i个强盗会在[a[i],a[i]+1],[a[i]+1,a[i]+2],...,[b[i]-1,b[i]]这么多段长度为1时间中选出一个时间进行抢劫,并计划抢走c[i]元.作为保安,你在每一段长度为1的时间内最多只能制止一个强盗,那么你最多可以挽回多少损失呢? 输入 第一行包含一个正整数n(1<=n<=5000),表示强盗的个数. 接下来n行,每行包含三个正整数a[i],b[i],c[i](1<=a[i]<b[i]<=5000,1<=c[i]

【bzoj3939】[Usaco2015 Feb]Cow Hopscotch 动态开点线段树优化dp

题目描述 Just like humans enjoy playing the game of Hopscotch, Farmer John's cows have invented a variant of the game for themselves to play. Being played by clumsy animals weighing nearly a ton, Cow Hopscotch almost always ends in disaster, but this has

【bzoj3073】[Pa2011]Journeys 线段树优化建图+堆优化Dijkstra

题目描述 Seter建造了一个很大的星球,他准备建造N个国家和无数双向道路.N个国家很快建造好了,用1..N编号,但是他发现道路实在太多了,他要一条条建简直是不可能的!于是他以如下方式建造道路:(a,b),(c,d)表示,对于任意两个国家x,y,如果a<=x<=b,c<=y<=d,那么在xy之间建造一条道路.Seter保证不会有一个国家与自己之间有道路. Seter好不容易建好了所有道路,他现在在位于P号的首都.Seter想知道P号国家到任意一个国家最少需要经过几条道路.当然,Se