构建器的贯彻
上面创建行为树的时光使用了构建器,下面我就介绍一下温馨之构建器实现
//行为树构建器,用来构建一棵行为树,通过前序遍历方式配合Back()和End()方法进行构建
class BehaviorTreeBuilder
{
public:
BehaviorTreeBuilder() { }
~BehaviorTreeBuilder() { }
BehaviorTreeBuilder* Sequence();
BehaviorTreeBuilder* Action(EActionMode ActionModes);
BehaviorTreeBuilder* Condition(EConditionMode ConditionMode,bool IsNegation);
BehaviorTreeBuilder* Selector();
BehaviorTreeBuilder* Repeat(int RepeatNum);
BehaviorTreeBuilder* ActiveSelector();
BehaviorTreeBuilder* Filter();
BehaviorTreeBuilder* Parallel(EPolicy InSucess, EPolicy InFailure);
BehaviorTreeBuilder* Monitor(EPolicy InSucess, EPolicy InFailure);
BehaviorTreeBuilder* Back();
BehaviorTree* End();
private:
void AddBehavior(Behavior* NewBehavior);
private:
Behavior* TreeRoot=nullptr;
//用于存储节点的堆栈
std::stack<Behavior*> NodeStack;
};
BehaviorTreeBuilder* BehaviorTreeBuilder::Sequence()
{
Behavior* Sq=Sequence::Create();
AddBehavior(Sq);
return this;
}
void BehaviorTreeBuilder::AddBehavior(Behavior* NewBehavior)
{
assert(NewBehavior);
//如果没有根节点设置新节点为根节点
if (!TreeRoot)
{
TreeRoot=NewBehavior;
}
//否则设置新节点为堆栈顶部节点的子节点
else
{
NodeStack.top()->AddChild(NewBehavior);
}
//将新节点压入堆栈
NodeStack.push(NewBehavior);
}
BehaviorTreeBuilder* BehaviorTreeBuilder::Back()
{
NodeStack.pop();
return this;
}
BehaviorTree* BehaviorTreeBuilder::End()
{
while (!NodeStack.empty())
{
NodeStack.pop();
}
BehaviorTree* Tmp= new BehaviorTree(TreeRoot);
TreeRoot = nullptr;
return Tmp;
}
于上面的贯彻中,我以每个方法里创建对诺节点,检测时是不是有清节点,如果没则拿其若为根节点,如果起则将那个设为堆栈顶部节点的子节点,随后用那压入堆栈,每次调用back则退栈,每个创建节点的章程都回去this以好调用下一个法,最后经过End()表示作为培训创建好并赶回构建好的作为培训。
这就是说地方就是行为树的介绍和贯彻了,下同样首我们拿对作为培训进行优化,慢慢进入次替行为培训。
[github地址][1]
行为培训简介
行培训是一律栽树状的数据结构,树上的各个一个节点都是一个行事。每次调用会从根节点开始遍历,通过检查行为的尽状态来实施不一之节点。他的亮点是耦合度低扩展性强,每个行为可跟外表现完全独立。目前的所作所为培训都得以用几任意架构(如规划器,效用论等)应用于AI之上。
class BehaviorTree
{
public:
BehaviorTree(Behavior* InRoot) { Root = InRoot; }
void Tick()
{
Root->Tick();
}
bool HaveRoot() { return Root?true:false; }
void SetRoot(Behavior* InNode) { Root= InNode; }
void Release() { Root->Release(); }
private:
Behavior* Root;
};
地方提供了行为树的兑现,行为培训起一个根节点和一个Tick()方法,在耍经过中每个一段时间会调用依次Tick方法,令行为培训起根节点开始实行。
示例
这边我创建了同样称作角色,该角色一样开始处于巡逻状态,一旦发觉敌人,先检查自己生命值是否过小,如果是就是逃避跑,否则就算攻击敌人,攻击过程中一旦身价值过小吗会见半途而废攻击,立即逃,如果敌人死亡则这终止攻击,这里我们用了构建器来创造了相同棵行为培训,关于构建器的兑现后会说到,这里每个函数创建了对应函数名的节点,
//构建行为树:角色一开始处于巡逻状态,一旦发现敌人,先检查自己生命值是否过低,如果是就逃跑,否则就攻击敌人,攻击过程中如果生命值过低也会中断攻击,立即逃跑,如果敌人死亡则立即停止攻击
BehaviorTreeBuilder* Builder = new BehaviorTreeBuilder();
BehaviorTree* Bt=Builder
->ActiveSelector()
->Sequence()
->Condition(EConditionMode::IsSeeEnemy,false)
->Back()
->ActiveSelector()
-> Sequence()
->Condition(EConditionMode::IsHealthLow,false)
->Back()
->Action(EActionMode::Runaway)
->Back()
->Back()
->Monitor(EPolicy::RequireAll,EPolicy::RequireOne)
->Condition(EConditionMode::IsEnemyDead,true)
->Back()
->Action(EActionMode::Attack)
->Back()
->Back()
->Back()
->Back()
->Action(EActionMode::Patrol)
->End();
delete Builder;
接下来我经过一个循环往复模拟行为树的施行。同时以列条件节点内通过任意数表示原则是否实施成功(具体见文末github源码)
//模拟执行行为树
for (int i = 0; i < 10; ++i)
{
Bt->Tick();
std::cout << std::endl;
}
尽结果如下,由于自由数之在每次执行结果都未一致
选择器(Selector)
选择器(Selector)是外一样栽常用的复合行为,它见面挨个执行每个子行为直到其中一个成执行或者全部失败了
由和顺序器仅仅是Update函数不同,下面仅贴发Update方法
EStatus Selector::Update()
{
while (true)
{
EStatus s = (*CurrChild)->Tick();
if (s != EStatus::Failure)
return s;
//如果执行失败了就继续执行,否则返回
if (++CurrChild == Childern.end())
return EStatus::Failure;
}
return EStatus::Invalid; //循环意外终止
}
行为培训简介
表现培训是平等种树状的数据结构,树上的各级一个节点都是一个行为。每次调用会从根节点开始遍历,通过检查行为之履状态来执行不同的节点。他的长是耦合度低扩展性强,每个行为好与其他行为了独立。目前底作为培训都可以以几任意架构(如规划器,效用论等)应用于AI之上。
class BehaviorTree
{
public:
BehaviorTree(Behavior* InRoot) { Root = InRoot; }
void Tick()
{
Root->Tick();
}
bool HaveRoot() { return Root?true:false; }
void SetRoot(Behavior* InNode) { Root= InNode; }
void Release() { Root->Release(); }
private:
Behavior* Root;
};
上面提供了行为树的实现,行为培训生一个根节点和一个Tick()方法,在玩过程被每个一段时间会调用依次Tick方法,令行为培训于根节点开始执行。
装饰器(Decorator)
装饰器(Decorator)是止来一个子节点的一言一行,顾名思义,装饰即是以子节点的本来逻辑上增添细节(如重执行子节点,改变子节点返回状态相当)
//装饰器
class Decorator :public Behavior
{
public:
virtual void AddChild(Behavior* InChild) { Child=InChild; }
protected:
Decorator() {}
virtual ~Decorator(){}
Behavior* Child;
};
贯彻了装饰器基类,下面我们来落实产实际的装饰器,也就是端提到的又执行多次子节点的装饰器
class Repeat :public Decorator
{
public:
static Behavior* Create(int InLimited) { return new Repeat(InLimited); }
virtual void Release() { Child->Release(); delete this; }
protected:
Repeat(int InLimited) :Limited(InLimited) {}
virtual ~Repeat(){}
virtual void OnInitialize() { Count = 0; }
virtual EStatus Update()override;
virtual Behavior* Create() { return nullptr; }
protected:
int Limited = 3;
int Count = 0;
};
正好使上面提到的,Create函数负责创建节点,Release负责释放
里面Update()方法的贯彻如下
EStatus Repeat::Update()
{
while (true)
{
Child->Tick();
if (Child->IsRunning())return EStatus::Success;
if (Child->IsFailuer())return EStatus::Failure;
if (++Count == Limited)return EStatus::Success;
Child->Reset();
}
return EStatus::Invalid;
}
逻辑很简单,如果实行破产就立刻回,执行着就是继续执行,执行成功便管计数器+1双重执行
复合行为
咱用行培训被装有多独子节点的行称为复合节点,通过复合节点我们好将简单节点组合也还有趣又扑朔迷离的行为逻辑。
脚实现了一个可节点的基类,将部分公用的点子在了内(如添加清除子节点等)
//复合节点基类
class Composite:public Behavior
{
virtual void AddChild(Behavior* InChild) override{Childern.push_back(InChild);}
void RemoveChild(Behavior* InChild);
void ClearChild() { Childern.clear(); }
virtual void Release()
{
for (auto it : Childern)
{
it->Release();
}
delete this;
}
protected:
Composite() {}
virtual ~Composite() {}
using Behaviors = std::vector<Behavior*>;
Behaviors Childern;
};
监视器(Monitor)
监视器是连行器的使用之一,通过以表现运行过程遭到不止检查是不是满足某条件,如果无饱则立即退出。将规范在并行器的尾部即可。
监视器(Monitor)
监视器是连行器的行使之一,通过以表现运行过程被不断检查是不是满足某条件,如果非饱则马上退出。将标准化在并行器的尾部即可。
于达到古老卷轴中丰富多彩的人士,到NBA2K中写汗水的球员,从使命召唤着诡计多端的仇,到刺客信条中活跃的人流。游戏AI几乎有于游戏中的每个角落,默默构建出一个教人向往的特大游戏世界。
这就是说这些扑朔迷离的AI又是怎么落实之吧?下面就是受咱来打听并亲手促成转玩AI基础架构之一的所作所为培训。
条件
原则一致是行为树的叶子节点,用于查看游戏世界信息(如仇人是否以抨击范围外,周围是否发生可攀爬物体等),通过返回状态表示法的成。
//条件基类
class Condition :public Behavior
{
public:
virtual void Release() { delete this; }
protected:
Condition(bool InIsNegation):IsNegation(InIsNegation) {}
virtual ~Condition() {}
protected:
//是否取反
bool IsNegation=false;
};
此我实现了原则基类,一个IsNegation来标识规范是否取反(比如是否看见敌人得以变成是否没见敌人)
复合行为
俺们将作为培训被有着多只子节点的行为称为复合节点,通过复合节点我们可以简单节点组合呢再次幽默又复杂的一言一行逻辑。
下面实现了一个抱节点的基类,将一部分公用的法在了中(如添加清除子节点等)
//复合节点基类
class Composite:public Behavior
{
virtual void AddChild(Behavior* InChild) override{Childern.push_back(InChild);}
void RemoveChild(Behavior* InChild);
void ClearChild() { Childern.clear(); }
virtual void Release()
{
for (auto it : Childern)
{
it->Release();
}
delete this;
}
protected:
Composite() {}
virtual ~Composite() {}
using Behaviors = std::vector<Behavior*>;
Behaviors Childern;
};
动作(Action)
动作是行为树的纸牌节点,表示角色做的具体操作(如攻击,上弹,防御等),负责反戏世界之状态。动作节点可径直接轨自Behavior节点,通过落实不同之Update()方法实现不同的逻辑,在OnInitialize()方法被获取数据和资源,在OnTerminate中放出资源。
//动作基类
class Action :public Behavior
{
public:
virtual void Release() { delete this; }
protected:
Action() {}
virtual ~Action() {}
};
在此处自己实现了一个动作基类,主要是以一个公用的Release方法负责释放节点内存空间,所有动作节点均只是连续自是办法
构建器的贯彻
上面创建行为树的时利用了构建器,下面我就是介绍一下自己的构建器实现
//行为树构建器,用来构建一棵行为树,通过前序遍历方式配合Back()和End()方法进行构建
class BehaviorTreeBuilder
{
public:
BehaviorTreeBuilder() { }
~BehaviorTreeBuilder() { }
BehaviorTreeBuilder* Sequence();
BehaviorTreeBuilder* Action(EActionMode ActionModes);
BehaviorTreeBuilder* Condition(EConditionMode ConditionMode,bool IsNegation);
BehaviorTreeBuilder* Selector();
BehaviorTreeBuilder* Repeat(int RepeatNum);
BehaviorTreeBuilder* ActiveSelector();
BehaviorTreeBuilder* Filter();
BehaviorTreeBuilder* Parallel(EPolicy InSucess, EPolicy InFailure);
BehaviorTreeBuilder* Monitor(EPolicy InSucess, EPolicy InFailure);
BehaviorTreeBuilder* Back();
BehaviorTree* End();
private:
void AddBehavior(Behavior* NewBehavior);
private:
Behavior* TreeRoot=nullptr;
//用于存储节点的堆栈
std::stack<Behavior*> NodeStack;
};
BehaviorTreeBuilder* BehaviorTreeBuilder::Sequence()
{
Behavior* Sq=Sequence::Create();
AddBehavior(Sq);
return this;
}
void BehaviorTreeBuilder::AddBehavior(Behavior* NewBehavior)
{
assert(NewBehavior);
//如果没有根节点设置新节点为根节点
if (!TreeRoot)
{
TreeRoot=NewBehavior;
}
//否则设置新节点为堆栈顶部节点的子节点
else
{
NodeStack.top()->AddChild(NewBehavior);
}
//将新节点压入堆栈
NodeStack.push(NewBehavior);
}
BehaviorTreeBuilder* BehaviorTreeBuilder::Back()
{
NodeStack.pop();
return this;
}
BehaviorTree* BehaviorTreeBuilder::End()
{
while (!NodeStack.empty())
{
NodeStack.pop();
}
BehaviorTree* Tmp= new BehaviorTree(TreeRoot);
TreeRoot = nullptr;
return Tmp;
}
于面的贯彻中,我当每个方法里创建对承诺节点,检测时是不是发生清节点,如果没有则用那如果为根节点,如果出则将该设为堆栈顶部节点的子节点,随后用那个压入堆栈,每次调用back则退栈,每个创建节点的方式还回来this以利于调用下一个道,最后经过End()表示作为培训创建好并回构建好之表现培训。
那点就行为树的牵线及实现了,下同样首我们以针对作为培训进行优化,慢慢进入次代行为培训。
[github地址][1]
顺序器(Sequence)
顺序器(Sequence)是复合节点的平栽,它逐个执行每个子行为,直到所有子行为实行成功或者来一个告负了。
//顺序器:依次执行所有节点直到其中一个失败或者全部成功位置
class Sequence :public Composite
{
public:
virtual std::string Name() override { return "Sequence"; }
static Behavior* Create() { return new Sequence(); }
protected:
Sequence() {}
virtual ~Sequence(){}
virtual void OnInitialize() override { CurrChild = Childern.begin();}
virtual EStatus Update() override;
protected:
Behaviors::iterator CurrChild;
};
里头Update()方法的实现如下
EStatus Sequence::Update()
{
while (true)
{
EStatus s = (*CurrChild)->Tick();
//如果执行成功了就继续执行,否则返回
if (s != EStatus::Success)
return s;
if (++CurrChild == Childern.end())
return EStatus::Success;
}
return EStatus::Invalid; //循环意外终止
}
并行器(Parallel)
顾名思义,并行器(Parallel)是同一栽于多个表现并行执行的节点。但仔细观察就会发现实际就是她们的创新函数在平帧被数调用而已。
//并行器:多个行为并行执行
class Parallel :public Composite
{
public:
static Behavior* Create(EPolicy InSucess, EPolicy InFailure){return new Parallel(InSucess, InFailure); }
virtual std::string Name() override { return "Parallel"; }
protected:
Parallel(EPolicy InSucess, EPolicy InFailure) :SucessPolicy(InSucess), FailurePolicy(InFailure) {}
virtual ~Parallel() {}
virtual EStatus Update() override;
virtual void OnTerminate(EStatus InStatus) override;
protected:
EPolicy SucessPolicy;
EPolicy FailurePolicy;
};
此处的Epolicy是一个枚举类型,表示成功与黄的准(是水到渠成或者破产一个尚是不折不扣打响或黄)
//Parallel节点成功与失败的要求,是全部成功/失败,还是一个成功/失败
enum class EPolicy :uint8_t
{
RequireOne,
RequireAll,
};
update函数实现如下
EStatus Parallel::Update()
{
int SuccessCount = 0, FailureCount = 0;
int ChildernSize = Childern.size();
for (auto it : Childern)
{
if (!it->IsTerminate())
it->Tick();
if (it->IsSuccess())
{
++SuccessCount;
if (SucessPolicy == EPolicy::RequireOne)
{
it->Reset();
return EStatus::Success;
}
}
if (it->IsFailuer())
{
++FailureCount;
if (FailurePolicy == EPolicy::RequireOne)
{
it->Reset();
return EStatus::Failure;
}
}
}
if (FailurePolicy == EPolicy::RequireAll&&FailureCount == ChildernSize)
{
for (auto it : Childern)
{
it->Reset();
}
return EStatus::Failure;
}
if (SucessPolicy == EPolicy::RequireAll&&SuccessCount == ChildernSize)
{
for (auto it : Childern)
{
it->Reset();
}
return EStatus::Success;
}
return EStatus::Running;
}
以代码中,并行器每次换代都推行诸一个并未结束的分行为,并检讨成功与挫败条件,如果满足则随即回。
另外,当并行器满足条件提前离时,所有正在行之子行为耶应有及时叫停,我们在OnTerminate()函数中调用每个子节点的停方法
void Parallel::OnTerminate(EStatus InStatus)
{
for (auto it : Childern)
{
if (it->IsRunning())
it->Abort();
}
}
自从达古老卷轴中丰富多彩的人物,到NBA2K中修汗水的球员,从使命召唤着诡计多端的仇人,到刺客信条中图文并茂的人流。游戏AI几乎在于游戏中的每个角落,默默构建有一个使得人憧憬的极大游戏世界。
那这些纷繁的AI又是怎么落实之为?下面就被我们来打听并亲手促成转戏耍AI基础架构之一之一言一行培训。
再接再厉选择器
主动选择器是选择器的一样种植,与普通的选择器不同之是,主动选择器会不断的积极性检查已经做出的决定,并不断的品大优先级行为之势头,当大优先级行为使得时胡立即打断低优先级行为的实践(如在巡的经过中窥见敌人,即时中断巡逻,立即攻击敌人)。
那个Update()方法和OnInitialize方法实现如下
//初始化时将CurrChild初始化为子节点的末尾
virtual void OnInitialize() override { CurrChild = Childern.end(); }
EStatus ActiveSelector::Update()
{
//每次执行前先保存的当前节点
Behaviors::iterator Previous = CurrChild;
//调用父类OnInlitiallize函数让选择器每次重新选取节点
Selector::OnInitialize();
EStatus result = Selector::Update();
//如果优先级更高的节点成功执行或者原节点执行失败则终止当前节点的执行
if (Previous != Childern.end()&CurrChild != Previous)
{
(*Previous)->Abort();
}
return result;
}
装饰器(Decorator)
装饰器(Decorator)是单出一个子节点的表现,顾名思义,装饰即是在子节点的原逻辑上增添细节(如再次执行子节点,改变子节点返回状态相当)
//装饰器
class Decorator :public Behavior
{
public:
virtual void AddChild(Behavior* InChild) { Child=InChild; }
protected:
Decorator() {}
virtual ~Decorator(){}
Behavior* Child;
};
落实了装饰器基类,下面我们来促成产实际的装饰器,也不怕是面提到的还执行多次子节点的装饰器
class Repeat :public Decorator
{
public:
static Behavior* Create(int InLimited) { return new Repeat(InLimited); }
virtual void Release() { Child->Release(); delete this; }
protected:
Repeat(int InLimited) :Limited(InLimited) {}
virtual ~Repeat(){}
virtual void OnInitialize() { Count = 0; }
virtual EStatus Update()override;
virtual Behavior* Create() { return nullptr; }
protected:
int Limited = 3;
int Count = 0;
};
刚刚而上面提到的,Create函数负责创建节点,Release负责释放
里面Update()方法的落实如下
EStatus Repeat::Update()
{
while (true)
{
Child->Tick();
if (Child->IsRunning())return EStatus::Success;
if (Child->IsFailuer())return EStatus::Failure;
if (++Count == Limited)return EStatus::Success;
Child->Reset();
}
return EStatus::Invalid;
}
逻辑很简单,如果执行破产就顿时赶回,执行中尽管继续执行,执行成功便拿计数器+1又执行
行为(behavior)
作为(behavior)是表现培训最基础的定义,是几有行止培训节点的基类,是一个空洞接口,而而动作规范等节点则是其的现实性实现。
脚是Behavior的实现,省略掉了有些简的判断状态的计完整源码可以参考文尾的github链接
class Behavior
{
public:
//释放对象所占资源
virtual void Release() = 0;
//包装函数,防止打破调用契约
EStatus Tick();
EStatus GetStatus() { return Status; }
virtual void AddChild(Behavior* Child){};
protected:
//创建对象请调用Create()释放对象请调用Release()
Behavior():Status(EStatus::Invalid){}
virtual ~Behavior() {}
virtual void OnInitialize() {};
virtual EStatus Update() = 0;
virtual void OnTerminate(EStatus Status) {};
protected:
EStatus Status;
};
Behavior接口是兼备行为培训节点的中坚,且自己确定具备节点的布局和析构方法还必是protected,以预防在栈上创建对象,所有的节点目标通过Create()静态方法在积上创办,通过Release()方法销毁,由于Behavior是个抽象接口,故没有提供Create()方法,本接口满足如下契约
- 当Update方法为首不善调动用前,调用一糟糕OnInitialize函数,负责初始化等操作
- Update()方法在作为培训每次换代时调用都仅调用一不成。
- 当行不再处于运行状态时,调用一差OnTerminate(),并根据返回状态不同执行不同的逻辑
为确保契约不为打破,我们将立即三单方法包装在Tick()方法里。Tick()的兑现如下
//update方法被首次调用前执行OnInitlize方法,每次行为树更新时调用一次update方法
//当刚刚更新的行为不再运行时调用OnTerminate方法
if (Status != EStatus::Running)
{
OnInitialize();
}
Status = Update();
if (Status != EStatus::Running)
{
OnTerminate(Status);
}
return Status;
其间返回值Estatus是一个朵举值,表示节点运行状态。
enum class EStatus:uint8_t
{
Invalid, //初始状态
Success, //成功
Failure, //失败
Running, //运行
Aborted, //终止
};
并行器(Parallel)
顾名思义,并行器(Parallel)是一模一样种于多个表现并行执行的节点。但仔细观察就会发觉实际就是她们之翻新函数在平帧被反复调用而已。
//并行器:多个行为并行执行
class Parallel :public Composite
{
public:
static Behavior* Create(EPolicy InSucess, EPolicy InFailure){return new Parallel(InSucess, InFailure); }
virtual std::string Name() override { return "Parallel"; }
protected:
Parallel(EPolicy InSucess, EPolicy InFailure) :SucessPolicy(InSucess), FailurePolicy(InFailure) {}
virtual ~Parallel() {}
virtual EStatus Update() override;
virtual void OnTerminate(EStatus InStatus) override;
protected:
EPolicy SucessPolicy;
EPolicy FailurePolicy;
};
此处的Epolicy是一个枚举类型,表示成功与挫折的法(是水到渠成或者失败一个尚是不折不扣打响或破产)
//Parallel节点成功与失败的要求,是全部成功/失败,还是一个成功/失败
enum class EPolicy :uint8_t
{
RequireOne,
RequireAll,
};
update函数实现如下
EStatus Parallel::Update()
{
int SuccessCount = 0, FailureCount = 0;
int ChildernSize = Childern.size();
for (auto it : Childern)
{
if (!it->IsTerminate())
it->Tick();
if (it->IsSuccess())
{
++SuccessCount;
if (SucessPolicy == EPolicy::RequireOne)
{
it->Reset();
return EStatus::Success;
}
}
if (it->IsFailuer())
{
++FailureCount;
if (FailurePolicy == EPolicy::RequireOne)
{
it->Reset();
return EStatus::Failure;
}
}
}
if (FailurePolicy == EPolicy::RequireAll&&FailureCount == ChildernSize)
{
for (auto it : Childern)
{
it->Reset();
}
return EStatus::Failure;
}
if (SucessPolicy == EPolicy::RequireAll&&SuccessCount == ChildernSize)
{
for (auto it : Childern)
{
it->Reset();
}
return EStatus::Success;
}
return EStatus::Running;
}
以代码中,并行器每次换代都履行诸一个从未有过终结的分行为,并检讨成功与挫败条件,如果满足则就回。
此外,当并行器满足条件提前离时,所有方实践之子行为呢当立即被停止,我们当OnTerminate()函数中调用每个子节点的息方法
void Parallel::OnTerminate(EStatus InStatus)
{
for (auto it : Childern)
{
if (it->IsRunning())
it->Abort();
}
}
条件
标准化同是行为树的纸牌节点,用于查看游戏世界信息(如仇人是否在攻击范围外,周围是否生可攀爬物体等),通过返回状态表示法的成功。
//条件基类
class Condition :public Behavior
{
public:
virtual void Release() { delete this; }
protected:
Condition(bool InIsNegation):IsNegation(InIsNegation) {}
virtual ~Condition() {}
protected:
//是否取反
bool IsNegation=false;
};
此处自己实现了条件基类,一个IsNegation来标识规范是否取反(比如是否看见敌人好变成是否没见敌人)
积极选择器
当仁不让选择器是选择器的一致种植,与常见的选择器不同之是,主动选择器会不断的主动检查就做出的裁定,并不停的尝试大优先级行为的取向,当大优先级行为中时胡立即打断低优先级行为的实行(如正在巡的进程中发现敌人,即时中断巡逻,立即攻击敌人)。
那Update()方法及OnInitialize方法实现如下
//初始化时将CurrChild初始化为子节点的末尾
virtual void OnInitialize() override { CurrChild = Childern.end(); }
EStatus ActiveSelector::Update()
{
//每次执行前先保存的当前节点
Behaviors::iterator Previous = CurrChild;
//调用父类OnInlitiallize函数让选择器每次重新选取节点
Selector::OnInitialize();
EStatus result = Selector::Update();
//如果优先级更高的节点成功执行或者原节点执行失败则终止当前节点的执行
if (Previous != Childern.end()&CurrChild != Previous)
{
(*Previous)->Abort();
}
return result;
}
顺序器(Sequence)
顺序器(Sequence)是复合节点的一律种,它逐个执行每个子行为,直到所有子行为实行成功还是出一个砸了。
//顺序器:依次执行所有节点直到其中一个失败或者全部成功位置
class Sequence :public Composite
{
public:
virtual std::string Name() override { return "Sequence"; }
static Behavior* Create() { return new Sequence(); }
protected:
Sequence() {}
virtual ~Sequence(){}
virtual void OnInitialize() override { CurrChild = Childern.begin();}
virtual EStatus Update() override;
protected:
Behaviors::iterator CurrChild;
};
其中Update()方法的实现如下
EStatus Sequence::Update()
{
while (true)
{
EStatus s = (*CurrChild)->Tick();
//如果执行成功了就继续执行,否则返回
if (s != EStatus::Success)
return s;
if (++CurrChild == Childern.end())
return EStatus::Success;
}
return EStatus::Invalid; //循环意外终止
}
选择器(Selector)
选择器(Selector)是其余一样种常用之复合行为,它见面相继执行每个子行为直到其中一个遂施行要全体未果了
鉴于与顺序器仅仅是Update函数不同,下面就贴出Update方法
EStatus Selector::Update()
{
while (true)
{
EStatus s = (*CurrChild)->Tick();
if (s != EStatus::Failure)
return s;
//如果执行失败了就继续执行,否则返回
if (++CurrChild == Childern.end())
return EStatus::Failure;
}
return EStatus::Invalid; //循环意外终止
}
动作(Action)
动作是行为树的纸牌节点,表示角色做的具体操作(如攻击,上弹,防御等),负责转游戏世界之状态。动作节点可径直接轨自Behavior节点,通过落实不同的Update()方法实现不同之逻辑,在OnInitialize()方法被获取数据和资源,在OnTerminate中放出资源。
//动作基类
class Action :public Behavior
{
public:
virtual void Release() { delete this; }
protected:
Action() {}
virtual ~Action() {}
};
于这边我实现了一个动作基类,主要是为一个公用的Release方法负责释放节点内存空间,所有动作节点都只是连续自是方式
行为(behavior)
行事(behavior)是行培训最基础的概念,是几有行为培训节点的基类,是一个华而不实接口,而使动作规范相当节点则是她的切切实实贯彻。
下是Behavior的落实,省略掉了有的简便的论断状态的办法完整源码可以参见文尾的github链接
class Behavior
{
public:
//释放对象所占资源
virtual void Release() = 0;
//包装函数,防止打破调用契约
EStatus Tick();
EStatus GetStatus() { return Status; }
virtual void AddChild(Behavior* Child){};
protected:
//创建对象请调用Create()释放对象请调用Release()
Behavior():Status(EStatus::Invalid){}
virtual ~Behavior() {}
virtual void OnInitialize() {};
virtual EStatus Update() = 0;
virtual void OnTerminate(EStatus Status) {};
protected:
EStatus Status;
};
Behavior接口是有所行止培训节点的骨干,且本人确定具有节点的布局与析构方法都不能不是protected,以戒在栈上创建对象,所有的节点目标通过Create()静态方法在积上创办,通过Release()方法销毁,由于Behavior是单泛接口,故并未供Create()方法,本接口满足如下契约
- 于Update方法让首浅调动用前,调用一破OnInitialize函数,负责初始化等操作
- Update()方法以表现培训每次换代时调用都只调用一潮。
- 当作为不再处于运行状态时,调用一不行OnTerminate(),并根据返回状态不同执行不一之逻辑
为确保契约不深受打破,我们将立刻三单主意包装在Tick()方法里。Tick()的兑现如下
//update方法被首次调用前执行OnInitlize方法,每次行为树更新时调用一次update方法
//当刚刚更新的行为不再运行时调用OnTerminate方法
if (Status != EStatus::Running)
{
OnInitialize();
}
Status = Update();
if (Status != EStatus::Running)
{
OnTerminate(Status);
}
return Status;
里返回值Estatus是一个朵举值,表示节点运行状态。
enum class EStatus:uint8_t
{
Invalid, //初始状态
Success, //成功
Failure, //失败
Running, //运行
Aborted, //终止
};
示例
此处自己创建了一致名为角色,该角色同样开始处于巡逻状态,一旦发现敌人,先反省自己生命值是否过小,如果是就逃跑,否则即攻击敌人,攻击过程中要身价值了小也会暂停攻击,立即逃,如果敌人死亡则就终止攻击,这里我们利用了构建器来创造了同一棵行为培训,关于构建器的实现后会摆到,这里每个函数创建了针对应函数名字的节点,
//构建行为树:角色一开始处于巡逻状态,一旦发现敌人,先检查自己生命值是否过低,如果是就逃跑,否则就攻击敌人,攻击过程中如果生命值过低也会中断攻击,立即逃跑,如果敌人死亡则立即停止攻击
BehaviorTreeBuilder* Builder = new BehaviorTreeBuilder();
BehaviorTree* Bt=Builder
->ActiveSelector()
->Sequence()
->Condition(EConditionMode::IsSeeEnemy,false)
->Back()
->ActiveSelector()
-> Sequence()
->Condition(EConditionMode::IsHealthLow,false)
->Back()
->Action(EActionMode::Runaway)
->Back()
->Back()
->Monitor(EPolicy::RequireAll,EPolicy::RequireOne)
->Condition(EConditionMode::IsEnemyDead,true)
->Back()
->Action(EActionMode::Attack)
->Back()
->Back()
->Back()
->Back()
->Action(EActionMode::Patrol)
->End();
delete Builder;
然后自己透过一个循环模拟行为树的行。同时于各国条件节点内通过随机数表示原则是否推行成功(具体表现文末github源码)
//模拟执行行为树
for (int i = 0; i < 10; ++i)
{
Bt->Tick();
std::cout << std::endl;
}
执行结果如下,由于自由数的有每次执行结果尚且未均等