新手引导行为树

新手引导 = 行为树 + Lua + 配置(lua.table)
在当前项目中,采用的是代码写死节点方式,来实现新手引导的需求,所以在每次策划需要修改节点时,总是需要耗费大量的人力。前期因为项目进度紧张,所以一直没有重构。
如果现在上天再给我一次重新选择的机会,我会选择行为树,并通过配置的节点的方式来实现。

Guide UML 图.jpg
  • Behavior.lua 所有节点基类
local Behavior = class("Behavior")

function Behavior:ctor()
    self.status = BStatus.Invalid
end

function Behavior:GetStatus()
    return self.status
end

function Behavior:OnInitialize()

end

function Behavior:Update()
    return BStatus.Invalid
end

function Behavior:OnTerminate()

end

function Behavior:Release()

end

function Behavior:AddChild(child) 

end

function Behavior:Tick()
    if self.status ~= BStatus.Running then
        self:OnInitialize()
    end

    self.status = self:Update()

    if self.status ~= BStatus.Running then
        self:OnTerminate()
    end
    return self.status
end

function Behavior:Reset()
    self.status = BStatus.Invalid
end

function Behavior:Abort()
    self:OnTerminate()
    self.status = BStatus.Abort
end

function Behavior:IsTerminate()
    return self.status == BStatus.Success or self.status == BStatus.Failure
end

function Behavior:IsRunning()
    return self.status == BStatus.Running
end

function Behavior:IsSuccess()
    return self.status == BStatus.Success
end

function Behavior:IsFailure()
    return self.status == BStatus.Failure
end

function Behavior:Name()
    return ""
end

function Behavior:SetCoroutine(pCoroutine)
    self.pCoroutine = pCoroutine
end

function Behavior:ResumeCoroutine()
    if self.pCoroutine then
        coroutine.resume(self.pCoroutine)
    end
end

return Behavior
  • Composite.lua 组合节点
local Composite = class("Composite",Module.Behavior)

function Composite:ctor()
    self.childrenList = {}
    Composite.super.ctor(self)
end

function Composite:AddChild(child)
    table.insert(self.childrenList,child)
end

function Composite:RemoveChild(child)
    table.remove(self.childrenList,child)
end

function Composite:ClearChild()
    for k,v in pairs(self.childrenList) do
        v:Release()
    end
    self.childrenList = {}
end

function Composite:getChild()
    if self:getChildrenNum() > 0 then
        return self.childrenList[self:getChildrenNum()]
    end
end

function Composite:getChildrenNum()
    return #self.childrenList
end

return Composite
  • Sequence.lua 顺序执行节点,因为引导过程是一个持续性过程,所以中间需要等待上一个节点完成,才能进行下一个。这里采用是lua 协程机制,来实现上一个节点完成后,继续执行下一个节点的逻辑。
local Sequence = class("Sequence",Module.Composite)

function Sequence:ctor()
    Sequence.super.ctor(self)
end

function Sequence:Update()
    local co = nil
    local coroutineFunc = function () 
        for i,v in ipairs(self.childrenList) do
            v:SetCoroutine(co)
            local status = v:Tick()
            printLG("Sequence:Update()",v:Name(),status)
            if status ~= BStatus.Success then
                coroutine.yield()
            end
        end
    end
    co = coroutine.create(coroutineFunc)
    coroutine.resume(co)
end

function Sequence:Name()
    return "Sequence"
end

return Sequence
  • ActionWaitFrame.lua 真实动作行为节点
local ActionWaitFrame = class("ActionWaitFrame",Module.Action)

function ActionWaitFrame:ctor(delayTime)
    self.delayTime = delayTime
    ActionWaitFrame.super.ctor(self)
end

function ActionWaitFrame:OnInitialize()
    self.delayKey = FrameTimerManager:delayCall(function()
        self:ResumeCoroutine()
    end, self.delayTime)
end

function ActionWaitFrame:Update()
    return BStatus.Running
end

function ActionWaitFrame:Release()
    FrameTimerManager:removeTimer(self.delayKey)
end

function ActionWaitFrame:Name()
    return "ActionWaitFrame"
end

return ActionWaitFrame
  • ConditionIsInTargetScene.lua 条件节点,判断是否到达目标场景,当满足条件时,通过ResumeCoroutine() 启动协程,执行下一个节点。
local ConditionIsInTargetScene = class("ConditionIsInTargetScene",Module.Condition)

function ConditionIsInTargetScene:ctor(targetViewTypeList)
    self.targetViewTypeList = targetViewTypeList
    ConditionIsInTargetScene.super.ctor(self)
end

function ConditionIsInTargetScene:Update()
    if AlertManager:checkIsTop(self.targetViewTypeList) then
        return BStatus.Success
    end
    self:mDoAddDetectScene(self.targetViewTypeList)
    return BStatus.Running
end

-- 侦测进场景
function ConditionIsInTargetScene:mDoAddDetectScene( viewTypeOrList)
    local viewTypeList = isTable(viewTypeOrList) and viewTypeOrList or {viewTypeOrList}
    local detectSceneKey = nil --不要跟下面一行合并 注意此处写法
    detectSceneKey = AlertManager:detectEnterSomeScene(
        viewTypeList, 
        function() 
            if detectSceneKey then 
                AlertManager:removeEnterSceneDetector(detectSceneKey)
                detectSceneKey = nil
            end
            self:ResumeCoroutine()
        end, true, 0)
    self.detectSceneKey = detectSceneKey
end


function ConditionIsInTargetScene:Release()
    if self.detectSceneKey then 
        AlertManager:removeEnterSceneDetector(self.detectSceneKey)
        self.detectSceneKey = nil
    end
end

function ConditionIsInTargetScene:Name()
    return "ConditionIsInTargetScene"
end

return ConditionIsInTargetScene

上面是部分实现代码,只供参考,具体内部的实现,还得根据自己项目的特点进行修改。

  • 配置文件采用是Lua table
local t = {
    {
        name = "Sequence",
        child = {
            {name = "ConditionIsInTargetScene", param = {{ViewType.MAIN_SCENE}}},
            {name = "ActionShowDialog", param = {"这是第一句话"}},
            {name = "ActionShowDialog", param = {'这是第二句话'}},
            {
                name = "Parallel",
                param = {EPolicy.RequireAll,EPolicy.RequireAll},
                child = {
                    {name = "ActionShowDialog", param = {'这是第三句话'}},
                    {name = "ActionWaitFrame", param = {2}},
                }
            },
            {name = "ActionShowGuideBtn", param = {"点击pvpBtnMain","pvpBtnMain",false}},
            {name = "ActionShowGuideBtn", param = {"点击sportBtn","sportBtn",false}},
            {name = "ConditionIsInTargetScene", param = {{RoomConstant.PVP_SCENE}}},
            {name = "ActionShowDialog", param = {'这是第四句话'}},
            {name = "ActionShowGuideBtn", param = {"点击startMovie","startMovie",false}},
        }
    }
}

return t
  • 解析代码是使用递归,不断向下找到所有子节点后递归。
local GuideParse = class("GuideParse")
local GuideData = require("LuaScript.gameModules.newGuide.data.GuideData")

function GuideParse:ctor()
end

function GuideParse:GetData()
    local data = self:Parse(nil,GuideData)
    return data
end

function GuideParse:Parse(parent,t)
    for key,val in ipairs(t) do
        local node = nil 
        if val.param then 
            print(key,val,unpack(val.param))
            node = Module[val.name]:new(unpack(val.param))
        else
            node = Module[val.name]:new()
        end
        if parent then
            parent:AddChild(node)
        else
            parent = node
        end
        if val.child then
            self:Parse(node,val.child)
        end
    end
    return parent
end

return GuideParse
  • 最后通过构建完整的行为树,并从根节点开始轮询,直到所有大节点都触发完成。
local GuideBehaviorTree = class("GuideBehaviorTree")

function GuideBehaviorTree:ctor()
    self.status = BStatus.Invalid
    self.guideParse = Module.GuideParse.new()
    self.treeRoot = self:Builder()
end

function GuideBehaviorTree:Builder()
    return self.guideParse:GetData()
end

function GuideBehaviorTree:Abort()
    self.status = BStatus.Abort
end

function GuideBehaviorTree:Tick()
    if (self.status == BStatus.Invalid or self.status == BStatus.Failure) and self.status ~= BStatus.Running then
        self.treeRoot:Tick()
        self.status = BStatus.Running
    end
end

function GuideBehaviorTree:reStart()
    self.status = BStatus.Invalid
end

return GuideBehaviorTree

参考:
行为树(Behavior Tree)实践(1)– 基本概念
游戏AI—行为树研究及实现

你可能感兴趣的:(新手引导行为树)