遊戲AI,行爲樹,Lua框架

行爲樹(縮寫BT),故名思議是一個樹狀結構,它是用樹的方式來描述一個角色的行爲。書本上的一些概念就不進行說明了。(本文僅代表個人理解的一個簡易版的行爲樹框架,適用於輕量級的AI邏輯,處理不當的地方還請指出)
下面直接介紹下行爲樹框架的大體組成:
在這裏插入圖片描述
框架組成主要由下面幾部分:

BTNode - 節點基類
  1. 功能:爲各種節點提供基礎函數
  2. 基礎函數:AddChild、RemoveChild、Clear、 Tick、Check、Evaluate等
BTAction - 行爲節點
  1. 功能:具體行爲執行的邏輯
  2. 基礎函數:Enter、Execute、Exit
BTControl - 控制節點
  1. 功能:控制子節點執行方式
  2. 分類:
    • Selector:選擇執行,按照規則選擇符合要求的子節點執行
    • Sequence:順序執行,按順序依次執行子節點
    • Parallel:並行執行,同時執行所有子節點
BTPercondation - 准入條件
  1. 功能:跟隨節點走,判斷當前節點是否可以執行
BTTree - 使用接口
  1. 功能:
    • 供外部使用
    • 行爲樹的整體啓動入口

以上就是一個行爲樹的大體框架組成,按照需求控制節點可以選擇適用的方式。


在這裏插入圖片描述
上圖是一個簡單地行爲樹,從圖中可以看出各個節點的關係狀態。下面講解下樹的運行:
大前提是每個先判斷控制節點的准入條件,然後再進行控制

  1. 樹的每次運行,都從根節點開始Root,在上面的這個樹中,根節點是一個選擇節點,根據選擇條件會選擇Action_1或者BTSequence_1進行執行,無論是哪個節點,都將結果返回給Root
  2. BTSequence_1是一個順序執行節點,先執行Action_2,返回成功後執行BTParallel_1,順序執行中,前一個節點返回成功後,再執行下一個節點,所有節點返回成功後,父節點返回成功。
  3. BTParallel_1是一個並行節點,同時執行所有子節點,任何一個子節點返回失敗,則父節點返回失敗,所有子節點返回成功,父節點返回成功。

以上就是一個簡易的行爲樹的介紹,行爲樹是一個針對遊戲AI的解決方案,常常和有限狀態機進行比較使用。行爲樹遠不止以上方面,更進階的使用還有很多方面:

  1. 多種類型的選擇節點
  2. 節點的前置、後置附件
  3. 循環節點、等待節點 …
  4. 事件子樹

下面給上代碼:
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,按照需求創建樹結構就可以,具體的准入條件和行爲節點的邏輯需要額外編寫,例如CowAICheckDistance就是准入條件的一種,繼承了BTPercondationIdleAction,RunAction,FlyAction就是具體的行爲腳本,都繼承了BTAction

注:

以上腳本中defClass,defClassStatic,ctor,Event.add函數是項目框架中的函數,不要糾結於這些函數,根據自己框架改進即可。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章