Sequence的继承关系如下:
Sequence->Composite->ParentTask->Task
上一篇已经实现了简单版本的ParentTask和Task(基于Behavior Designer的源码),那么接下来看下Composite和Sequence。
1.Composite:表明该节点是组合节点,无特殊作用。
2.Sequence:
成员:
currentChildIndex:当前运行的子节点索引
executionStatus:当前运行的子节点状态
方法:
CanExecute:当有可运行的子节点并且子节点不返回失败时,返回true
OnChildExecuted:当一个子节点运行完后调用,将当前索引指向下一个节点,同时更新当前运行的子节点状态
OnEnd:Sequence运行结束时调用,重置变量
namespace BehaviorDesigner.Runtime.Tasks
{
[TaskDescription("The sequence task is similar to an \"and\" operation. It will return failure as soon as one of its child tasks return failure. " +
"If a child task returns success then it will sequentially run the next task. If all child tasks return success then it will return success.")]
[HelpURL("http://www.opsive.com/assets/BehaviorDesigner/documentation.php?id=25")]
[TaskIcon("{SkinColor}SequenceIcon.png")]
public class Sequence : Composite
{
// The index of the child that is currently running or is about to run.
private int currentChildIndex = ;
// The task status of the last child ran.
private TaskStatus executionStatus = TaskStatus.Inactive; public override int CurrentChildIndex()
{
return currentChildIndex;
} public override bool CanExecute()
{
// We can continue to execuate as long as we have children that haven't been executed and no child has returned failure.
return currentChildIndex < children.Count && executionStatus != TaskStatus.Failure;
} public override void OnChildExecuted(TaskStatus childStatus)
{
// Increase the child index and update the execution status after a child has finished running.
currentChildIndex++;
executionStatus = childStatus;
} public override void OnConditionalAbort(int childIndex)
{
// Set the current child index to the index that caused the abort
currentChildIndex = childIndex;
executionStatus = TaskStatus.Inactive;
} public override void OnEnd()
{
// All of the children have run. Reset the variables back to their starting values.
executionStatus = TaskStatus.Inactive;
currentChildIndex = ;
}
}
}
当然,因为Behavior Designer源码的代码量比较多,所以不可能完全地实现一个Lua版本的BD,不过可以参考里面的一些设计思想去实现一套行为树。
以Sequence节点为例:
1.如果当前节点返回Failure,则Sequence返回Failure,结束执行Sequence
2.如果当前节点返回Running,则Sequence返回Running,下一帧继续执行当前节点
3.如果当前节点返回Success,那么Sequence将会执行下一个节点,这里会出现两种做法,一是Sequence返回Running,等下一帧再执行下一节点;二是在同一帧下继续执行。这里我采用的是第二种,因为在早期的AI中,逻辑都是在Update中进行的,也就是逻辑都在同一帧中,因此我觉得放在同一帧中执行效率会高一些。
代码如下:
BTParentTask.lua
BTParentTask = BTTask:New(); local this = BTParentTask; function this:New()
local o = {};
setmetatable(o, self);
self.__index = self;
o.currentChildIndex = ; --当前运行到第几个子节点
o.currentChildTask = nil; --当前运行的子节点
o.childTasks = {}; --子节点列表
return o;
end function this:AddChild(task)
local index = #self.childTasks + ;
task.index = index;
task.layer = self.layer + ;
task.parent = self;
task.root = self.root;
self.childTasks[index] = task;
end --是否有子节点
function this:HasChild()
if (#self.childTasks > ) then
return true;
else
return false;
end
end --获取下一个要执行的子节点
function this:GetNextChild()
if (#self.childTasks >= (self.currentChildIndex + )) then
self.currentChildIndex = self.currentChildIndex + ;
return self.childTasks[self.currentChildIndex];
end
return nil;
end --重置
function this:Reset()
self.currentChildIndex = ;
self.currentChildTask = nil;
end
BTSequence.lua
BTSequence = BTComposite:New(); local this = BTSequence; function this:New()
local o = {};
setmetatable(o, self);
self.__index = self;
o.executionStatus = BTTaskStatus.Inactive;
return o;
end function this:OnUpdate()
if (not self:HasChild()) then
return BTTaskStatus.Failure;
end if (not self.currentChildTask) then
self.currentChildTask = self:GetNextChild();
if (not self.currentChildTask) then
return BTTaskStatus.Failure;
end
end while (self.currentChildTask) do
self.executionStatus = self.currentChildTask:OnUpdate();
if (self.executionStatus == BTTaskStatus.Failure) then --结束
self:Reset();
return BTTaskStatus.Failure;
elseif (self.executionStatus == BTTaskStatus.Running) then --下一帧继续执行
return BTTaskStatus.Running;
else --继续执行下一节点
self.currentChildTask = self:GetNextChild();
end
end --所有节点执行完毕
self.executionStatus = BTTaskStatus.Success;
self:Reset();
return BTTaskStatus.Success;
end
TestBehaviorTree.lua
TestBehaviorTree = BTBehaviorTree:New(); local this = TestBehaviorTree; function this:New()
local o = {};
setmetatable(o, self);
self.__index = self;
this:Init();
return o;
end function this:Init()
local sequence = BTSequence:New();
local log = BTLog:New("hello world!");
local log2 = BTLog:New("This is a test");
sequence:AddChild(log);
sequence:AddChild(log2);
this:PushTask(sequence);
end
打印如下: