行爲樹(縮寫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
函數是項目框架中的函數,不要糾結於這些函數,根據自己框架改進即可。