小言_互联网的博客

游戏AI,行为树,Lua框架

292人阅读  评论(0)

行为树(缩写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函数是项目框架中的函数,不要纠结于这些函数,根据自己框架改进即可。


转载:https://blog.csdn.net/YuAnHandSome/article/details/106049081
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场