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