行为树(缩写BT),故名思议是一个树状结构,它是用树的方式来描述一个角色的行为。书本上的一些概念就不进行说明了。(本文仅代表个人理解的一个简易版的行为树框架,适用于轻量级的AI逻辑,处理不当的地方还请指出)
下面直接介绍下行为树框架的大体组成:
框架组成主要由下面几部分:
BTNode - 节点基类
- 功能:为各种节点提供基础函数
- 基础函数:AddChild、RemoveChild、Clear、 Tick、Check、Evaluate等
BTAction - 行为节点
- 功能:具体行为执行的逻辑
- 基础函数:Enter、Execute、Exit
BTControl - 控制节点
- 功能:控制子节点执行方式
- 分类:
- Selector:选择执行,按照规则选择符合要求的子节点执行
- Sequence:顺序执行,按顺序依次执行子节点
- Parallel:并行执行,同时执行所有子节点
BTPercondation - 准入条件
- 功能:跟随节点走,判断当前节点是否可以执行
BTTree - 使用接口
- 功能:
- 供外部使用
- 行为树的整体启动入口
以上就是一个行为树的大体框架组成,按照需求控制节点
可以选择适用的方式。
上图是一个简单地行为树,从图中可以看出各个节点的关系状态。下面讲解下树的运行:
大前提是每个先判断控制节点的准入条件,然后再进行控制
- 树的每次运行,都从根节点开始
Root
,在上面的这个树中,根节点是一个选择节点,根据选择条件会选择Action_1
或者BTSequence_1
进行执行,无论是哪个节点,都将结果返回给Root
BTSequence_1
是一个顺序执行节点,先执行Action_2
,返回成功后执行BTParallel_1
,顺序执行中,前一个节点返回成功后,再执行下一个节点,所有节点返回成功后,父节点返回成功。BTParallel_1
是一个并行节点,同时执行所有子节点,任何一个子节点返回失败,则父节点返回失败,所有子节点返回成功,父节点返回成功。
以上就是一个简易的行为树的介绍,行为树是一个针对游戏AI的解决方案,常常和有限状态机进行比较使用。行为树远不止以上方面,更进阶的使用还有很多方面:
- 多种类型的选择节点
- 节点的前置、后置附件
- 循环节点、等待节点 …
- 事件子树
…
下面给上代码:
BTNode.lua
local cls, super = defClass("BTNode")
cls.ctor = function(self, obj, percondation)
self.gameObject = obj
self.percondation = percondation
self.animator = self.gameObject:GetComponent(typeof(Animator))
self.lastChild = nil
self.childList = {}
end
cls.AddChild = function(self, child)
if not self.childList then
self.childList = {}
end
table.insert(self.childList, child)
end
cls.RemoveChild = function(self, child)
if not self.childList then
return
end
for i = 1, #self.childList do
if self.childList[i] == child then
self.childList[i]:Clear()
table.remove(self.childList, i)
return
end
end
end
cls.Evaluate = function(self)
local percondationCheck = true
if self.percondation then
percondationCheck = self.percondation:Check()
end
return self:DoEvaluate() and percondationCheck
end
cls.DoEvaluate = function(self)
return true
end
cls.Tick = function(self)
end
cls.Clear = function(self)
for i = #self.childList, -1, 1 do
self.childList[i]:Clear()
table.remove(self.childList, i)
end
end
return cls
BTAction.lua
local cls, super = defClass("BTAction", BTNode)
cls.State = {
Ready = "ready",
Running = "running",
}
cls.ctor = function(self, obj, percondation)
super.ctor(self, obj, percondation)
self.state = BTAction.State.Ready
end
cls.Enter = function(self)
end
cls.Execute = function(self)
return BTResult.SUCCESS
end
cls.Exit = function(self)
end
cls.Tick = function(self)
local result = BTResult.SUCCESS
if self.state == BTAction.State.Ready then
self:Enter()
self.state = BTAction.State.Running
end
if self.state == BTAction.State.Running then
result = self:Execute()
if result ~= BTResult.RUNNING then
self:Exit()
self.state = BTAction.State.Ready
end
end
return result
end
cls.Clear = function(self)
self:Exit()
self.state = cls.State.Ready
end
return cls
BTPercondation.lua
local cls, super = defClass("BTPercondation", BTNode)
cls.Check = function(self)
return true
end
return cls
BTPrioritySelector.lua
--[[
顺序遍历子节点,执行第一个符合准入条件的节点
]]
local cls, super = defClass("BTPrioritySelector", BTNode)
cls.DoEvaluate = function(self)
for i = 1, #self.childList do
local child = self.childList[i]
local eva = child:Evaluate()
if eva then
self.activeChild = child
return true
end
end
return false
end
cls.Tick = function(self)
if self.activeChild then
return self.activeChild:Tick()
end
return BTResult.SUCCESS
end
return cls
BTResult.lua
local cls = defClassStatic("BTResult")
cls.RUNNING = "running"
cls.SUCCESS = "success"
cls.FAILUER = "failuer"
return cls
BTTree.lua
local cls, super = defClass("BTTree")
cls.ctor = function(self, obj)
self.gameObject = obj
self.transform = obj.transform
self.animator = self.gameObject:GetComponent(typeof(Animator))
Event.add(self.gameObject, Event.FixedUpdate, self.FixedUpdate, self)
self.rootNode = nil
end
cls.FixedUpdate = function(self)
if not self.rootNode then
return
end
if self.rootNode:Evaluate() then
self.rootNode:Tick()
end
end
return cls
使用示例
CowAI.lua
require("BT/BTTree")
local cls, super = defClass("CowAI", BTTree)
cls.ctor = function(self, obj, target)
super.ctor(self, obj)
self.target = target
self:createBT()
end
-- 创建行为树
cls.createBT = function(self)
self.rootNode = BTPrioritySelector.new(self.gameObject)
self.subRootNode = BTPrioritySelector.new(self.gameObject)
self.runAction = RunAction.new(self.gameObject, CheckDistance.new(self.gameObject, self.target, "LESS", 1.76 + 1.5), self.target)
self.subRootNode:AddChild(self.runAction)
self.idleAction = IdleAction.new(self.gameObject, CheckDistance.new(self.gameObject, self.target, "LARGE", 1.76 + 1.5), self.target)
self.subRootNode:AddChild(self.idleAction)
self.flyAction = FlyAction.new(self.gameObject, CheckDistance.new(self.gameObject, self.target, "LESS", 1.3), self.target)
self.rootNode:AddChild(self.flyAction)
self.rootNode:AddChild(self.subRootNode)
end
return cls
使用时,继承BTTree
,按照需求创建树结构就可以,具体的准入条件和行为节点的逻辑需要额外编写,例如CowAI
中CheckDistance
就是准入条件的一种,继承了BTPercondation
;IdleAction
,RunAction
,FlyAction
就是具体的行为脚本,都继承了BTAction
注:
以上脚本中defClass
,defClassStatic
,ctor
,Event.add
函数是项目框架中的函数,不要纠结于这些函数,根据自己框架改进即可。
转载:https://blog.csdn.net/YuAnHandSome/article/details/106049081
查看评论