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