在前面一篇《状态机4,关于战斗中负责兵种状态切换的状态机》中实现了一个简单的状态机
在最后抛出了为什么说简单的问题,因为在一个复杂的战斗中,一个角色很少说只有一个状态,
或者说根本不可能,多状态是合理的需求,在之前实现的状态机中,角色同时只能有一个状态,
要么行走状态,要么冰冻状态,从代码逻辑上看,一个新的状态进来当前的状态会被顶替掉,
来看需求:一个角色的攻击的过程中中了一个中毒buff,在一段时间内会持续掉血,这种情况下
角色会同时有两个状态,攻击状态和中毒状态,中毒状态不会顶替攻击状态,在中毒状态下角色
也可以切换到行走状态,冰冻状态等等,那么从以上的需求来看,
我们可以把状态分为两种类型:主状态和子状态,
主状态:如待机状态,行走状态,攻击状态,冰冻状态等等,它们的特点是相互这间互斥,
角色在同一时刻只能处于其中一种状态,新的主状态进来会顶替当前的主状态
子状态:如中毒状态,变羊状态,隐身状态等等,它们的特点是相互之间独立,且不会顶替
掉主状态,如角色可以同时处于行走状态,中毒状态,隐身状态
经过上面的总结,我们把上一篇文章里提到的状态做下分类,并加入一些新的状态
主状态:待机状态,行走状态,攻击状态,冰冻状态,眩晕状态
子状态:中毒状态,网捕状态,变羊状态
总结完之后,大概说下新的状态机的原理,大部分跟之前的简单状态机一样,不同的地方在于切换
状态的时候,需要做一些额外的逻辑处理,
1.如果新进来的状态是子状态,那么直接新增一个子状态,并直接返回不做其他处理,
子状态可以独立添加和移除
2.如果新进来的状态是主状态,那么先检查一下角色身上的子状态,为什么要做检查,
下面说一下一些情况就能明白,
假如角色现在处于攻击状态,在此状态下中了一个网捕buff,网捕buff的作用是让角色
不能行走,带有网捕状态的角色只能呆在原地做攻击,待机等等行为,
再假如角色现在处于行走状态,在此状态下中了一个变羊的buff,变羊buff的作用是让角色
变成羊的模型,带有变羊状态的角色不能使用技能,不能攻击,其他如行走,冰冻是可以的
通过上面的例子可以总结出来,子状态的存在会影响到主状态的切换,那么我们的状态切换
规则就分三步:
1.如果新进来的状态是子状态,那么直接添加
2.如果新进来的状态是主状态,那么分下面几步:
如果是相同的状态,则直接返回
如果不是相同的状态,那么检查自身的子状态,是否有影响到主状态的切换的子状态
如果没有影响到主状态切换的子状态,那么检查是否有可切换的主状态
下面来看代码:
StateId.lua:状态id,没什么大的区别,只是新加了几个新的状态id,子状态从500开始,做区分
local StateId = {}
StateId.unKnown = -1
StateId.idle = 1 --待机
StateId.walk = 2 --行走
StateId.attack = 3 --攻击
StateId.frozen = 4 --冰冻
StateId.vertigo = 5 --眩晕
StateId.subStateStartId = 500 --子状态开始id
StateId.poisoning = 501 --中毒
StateId.netCatch = 502 --网捕
StateId.sheep = 503 --羊
StateId.name = {}
StateId.name[StateId.idle] = "待机状态"
StateId.name[StateId.walk] = "行走状态"
StateId.name[StateId.attack] = "攻击状态"
StateId.name[StateId.frozen] = "冰冻状态"
StateId.name[StateId.vertigo] = "眩晕状态"
StateId.name[StateId.poisoning] = "中毒状态"
StateId.name[StateId.netCatch] = "网捕状态"
StateId.name[StateId.sheep] = "羊状态"
return StateId
StateRuleConfig.lua:这个跟之前的有些区别,分为可切换的主状态和互斥的子状态
拿一个出来说一下,假设当前角色处于攻击状态(3)和网捕状态(502),要切换掉行走状态(2),
那么新进来的状态是主状态,要先判断是否有影响主状态切换的子状态,行走状态的id是2,那么取出
来的配置是:StateRuleConfig[2] = {majorStateCanToggle = {1, 3, 4, 5}, mutexSubState = {502}}
此时角色身上有网捕状态(502),在互斥配置中的相同的id,那么无法切换到行走状态
StateRuleConfig = {}
--状态id:待机状态(1),行走状态(2),攻击状态(3),冰冻状态(4),眩晕状态(5),中毒(501),网捕(502),羊(503)
--majorStateCanToggle:可切换的主状态
--mutexSubState:互斥子状态,
StateRuleConfig[1] = {majorStateCanToggle = {2, 3, 4, 5}, mutexSubState = {}}
StateRuleConfig[2] = {majorStateCanToggle = {1, 3, 4, 5}, mutexSubState = {502}}
StateRuleConfig[3] = {majorStateCanToggle = {1, 2, 4, 5}, mutexSubState = {503}}
StateRuleConfig[4] = {majorStateCanToggle = {1}, mutexSubState = {}}
StateRuleConfig[5] = {majorStateCanToggle = {1}, mutexSubState = {}}
return StateRuleConfig
StateRule.lua:状态切换规则,跟之前的版本有些不同,新加了互斥子状态的检查
local StateId = require("app.edition6.state.StateId")
local StateRule = class("StateRule")
function StateRule:ctor()
end
function StateRule:bindStateRuleConfig(tStateRuleConfig)
self.tStateRuleConfig = tStateRuleConfig
end
function StateRule:isCanToggle(iCurrentStateId, iNewStateId, tSubState)
--是否是相同的主状态
if self:isSameState(iCurrentStateId, iNewStateId) then return false end
--角色身上是否有与主状态互斥的子状态
local bHaveMutexSubState, iMutexSubStateId = self:isHaveMutexSubState(iNewStateId, tSubState)
--如果有互斥的子状态,将互斥的子状态的id返回,方便调试
if bHaveMutexSubState then return false, iMutexSubStateId end
--是否有可切换的主状态
return self:isCanToggleToNewState(iCurrentStateId, iNewStateId)
end
function StateRule:isSubState(iNewStateId)
return iNewStateId > StateId.subStateStartId
end
--是否跟当前状态相同
function StateRule:isSameState(iCurrentStateId, iNewStateId)
return iCurrentStateId == iNewStateId
end
--能否切换到新状态
function StateRule:isCanToggleToNewState(iCurrentStateId, iNewStateId)
local tCurrentStateRule = self.tStateRuleConfig[iCurrentStateId].majorStateCanToggle
if tCurrentStateRule == nil then return false end
for _, iStateId in pairs(tCurrentStateRule) do
if iStateId == iNewStateId then
return true
end
end
return false
end
--是否有角色身上带有互斥子状态
function StateRule:isHaveMutexSubState(iNewStateId, tSubState)
local tMutexSubStateRule = self.tStateRuleConfig[iNewStateId].mutexSubState
if tMutexSubStateRule == nil then return false end
if #tMutexSubStateRule == 0 then return false end
for _, iMutexSubStateId in pairs(tMutexSubStateRule) do
if self:isHaveSubState(tSubState, iMutexSubStateId) then
return true, iMutexSubStateId
end
end
return false
end
--是否有指定的子状态
function StateRule:isHaveSubState(tSubState, iSubStateId)
for _, subState in pairs(tSubState) do
if subState:getStateId() == iSubStateId then
return true
end
end
return false
end
return StateRule
上面我们把状态分为主状态和子状态,那么对应的将具体状态做区分
StateType.lua:状态类型,这个比较简单
local StateType = {}
StateType.unKnown = -1
StateType.major = 1 --主状态
StateType.sub = 2 --子状态
return StateType
StateBase.lua:状态基类,加了两个接口,比较简单,不多说
local StateId = require("app.edition6.state.StateId")
local StateType = require("app.edition6.state.StateType")
local StateBase = class("StateBase")
function StateBase:ctor()
self:setStateId(StateId.unKnown)
self:setStateType(StateType.unKnown)
end
function StateBase:bindSoldier(soldier)
self.soldier = soldier
end
--进入状态
function StateBase:onEnter(parms)
end
--退出状态
function StateBase:onExit()
end
function StateBase:update(dt)
end
function StateBase:setStateId(iStateId)
self.iStateId = iStateId
end
function StateBase:getStateId()
return self.iStateId
end
function StateBase:setStateType(iStateType)
self.iStateType = iStateType
end
function StateBase:getStateType()
return self.iStateType
end
return StateBase
MajorStateBase.lua:主状态基类,继承状态基类,比较简单,这样分也是有点用处的,下一篇再讲
local StateBase = require("app.edition6.state.StateBase")
local StateType = require("app.edition6.state.StateType")
--主状态
local MajorStateBase = class("MajorStateBase", function()
return StateBase.new()
end)
function MajorStateBase:ctor()
self:setStateType(StateType.major)
end
return MajorStateBase
SubStateBase.lua:子状态基类
local StateBase = require("app.edition6.state.StateBase")
local StateType = require("app.edition6.state.StateType")
--主状态
local SubStateBase = class("SubStateBase", function()
return StateBase.new()
end)
function SubStateBase:ctor()
self:setStateType(StateType.sub)
end
return SubStateBase
StateCreator.lua:状态创建器,只是新加了些状态而已
local StateId = require("app.edition6.state.StateId")
--主状态
local IdleState = require("app.edition6.state.majorState.IdleState")
local WalkState = require("app.edition6.state.majorState.WalkState")
local AttackState = require("app.edition6.state.majorState.AttackState")
local FrozenState = require("app.edition6.state.majorState.FrozenState")
local VertigoState = require("app.edition6.state.majorState.VertigoState")
--子状态
local PoisoningState = require("app.edition6.state.subState.PoisoningState")
local SheepState = require("app.edition6.state.subState.SheepState")
local NetCatchState = require("app.edition6.state.subState.NetCatchState")
local StateCreator = class("StateCreator")
function StateCreator:ctor()
end
function StateCreator:createState(iStateId)
local state = nil
if iStateId == StateId.idle then
state = IdleState.new()
elseif iStateId == StateId.walk then
state = WalkState.new()
elseif iStateId == StateId.attack then
state = AttackState.new()
elseif iStateId == StateId.frozen then
state = FrozenState.new()
elseif iStateId == StateId.vertigo then
state = VertigoState.new()
elseif iStateId == StateId.poisoning then
state = PoisoningState.new()
elseif iStateId == StateId.sheep then
state = SheepState.new()
elseif iStateId == StateId.netCatch then
state = NetCatchState.new()
end
return state
end
return StateCreator
IdleState.lua:待机状态(主状态),跟之前唯一的区别是继承主状态MajorStateBase,其他的主状态如攻击状态,行走状态类似
local MajorStateBase = require("app.edition6.state.MajorStateBase")
local StateId = require("app.edition6.state.StateId")
local IdleState = class("IdleState", function()
return MajorStateBase.new()
end)
function IdleState:ctor()
self:setStateId(StateId.idle)
end
function IdleState:onEnter(parms)
print("进入待机状态")
end
function IdleState:onExit()
print("退出待机状态")
end
return IdleState
PoisoningState.lua:中毒状态(子状态),继承SubStateBase,其他的子状态如变羊状态,网捕状态类似
local SubStateBase = require("app.edition6.state.SubStateBase")
local StateId = require("app.edition6.state.StateId")
local PoisoningState = class("PoisoningState", function()
return SubStateBase.new()
end)
function PoisoningState:ctor()
self:setStateId(StateId.poisoning)
end
function PoisoningState:onEnter(parms)
print("进入中毒状态")
end
function PoisoningState:onExit()
print("退出中毒状态")
end
return PoisoningState
StateMachine.lua:状态机,新增了addSubState()和removeSubState()和insertSubState()接口,removeSubState()
在兵种类中被调用,在toggleState()中做了修改,加入了子状态的逻辑判断
local StateId = require("app.edition6.state.StateId")
local StateMachine = class("StateMachine")
function StateMachine:ctor()
self.tSubState = {}
end
--绑定角色对象
function StateMachine:bindSoldier(soldier)
self.soldier = soldier
self:initDefaultState()
end
--初始化默认状态
function StateMachine:initDefaultState()
local defaultState = self:createState(StateId.idle)
self:setCurrentState(defaultState)
print("初始化默认状态为 ( " .. StateId.name[defaultState:getStateId()] .. " )")
end
--绑定状态切换规则
function StateMachine:bindStateRule(stateRule)
self.stateRule = stateRule
end
function StateMachine:bindStateCreator(stateCretor)
self.stateCretor = stateCretor
end
--设置当前状态
function StateMachine:setCurrentState(state)
self.currentState = state
end
function StateMachine:getCurrentState()
return self.currentState
end
function StateMachine:getCurrentStateId()
local iCurrentStateId = StateId.unKnown
if self.currentState ~= nil then
iCurrentStateId = self.currentState:getStateId()
end
return iCurrentStateId
end
function StateMachine:toggleState(iNewStateId, parms)
--是否是子状态,如果是,则直接加到子状态列表
local bSubState = self.stateRule:isSubState(iNewStateId)
if bSubState then
self:addSubState(iNewStateId, parms)
else
self:toggleMajorState(iNewStateId, parms)
end
end
--切换主状态
function StateMachine:toggleMajorState(iNewStateId, parms)
--不能切换,直接返回
local iCurrentStateId = self:getCurrentStateId()
local bCanToggleMajorState, iMutexSubStateId = self.stateRule:isCanToggle(iCurrentStateId, iNewStateId, self.tSubState)
if not bCanToggleMajorState then
self.soldier:toggleStateFail(iCurrentStateId, iNewStateId, iMutexSubStateId)
return
end
local newState = self:createState(iNewStateId)
--没有对应的状态,直接返回
if newState == nil then
self.soldier:toggleStateFail(iCurrentStateId, iNewStateId)
return
end
--退出当前状态
self:getCurrentState():onExit()
self:setCurrentState(newState)
--进入新状态
newState:onEnter(parms)
end
--新增子状态
function StateMachine:addSubState(iNewStateId, parms)
local subState = self:createState(iNewStateId)
--没有对应的状态,直接返回
if subState == nil then
self.soldier:addSubStateFail(iNewStateId)
return
end
subState:onEnter(parms)
self:insertSubState(subState)
end
function StateMachine:insertSubState(subState)
table.insert(self.tSubState, subState)
end
function StateMachine:removeSubState(iSubStateId)
for _, subState in pairs(self.tSubState) do
if subState:getStateId() == iSubStateId then
subState:onExit()
table.removebyvalue(self.tSubState, subState, true)
end
end
end
--创建状态
function StateMachine:createState(iNewStateId)
local newState = self.stateCretor:createState(iNewStateId)
if newState ~= nil then
newState:bindSoldier(self.soldier)
end
return newState
end
function StateMachine:isState(iStateId)
return self:getCurrentStateId() == iStateId
end
return StateMachine
Soldier.lua:兵种类,新加了一个removeSubState()接口,传入状态id,委托状态机移除子状态,为什么
有些XXXEnd()里面是调用onIdle(),而有些是removeSubState(),因为在设计状态时我们已经区别出哪些
是主状态,哪些是子状态,而主状态是切换,被顶替的特性,子状态的特点是直接添加或移除,所以调用不同
local StateId = require("app.edition6.state.StateId")
local Soldier = class("Soldier")
function Soldier:ctor()
end
--绑定状态机
function Soldier:bindStateMachine(stateMachine)
self.stateMachine = stateMachine
self.stateMachine:bindSoldier(self)
end
--待机
function Soldier:onIdle()
self:toggleState(StateId.idle)
end
--是否是待机状态
function Soldier:isIdle()
return self:isState(StateId.idle)
end
--行走
function Soldier:onWalk()
self:toggleState(StateId.walk)
end
--行走结束
function Soldier:onWalkEnd()
self:onIdle()
end
--是否是行走状态
function Soldier:isWalk()
return self:isState(StateId.walk)
end
--攻击
function Soldier:onAttack()
self:toggleState(StateId.attack)
end
--攻击结束
function Soldier:onAttackEnd()
self:onIdle()
end
--是否是攻击状态
function Soldier:isAttack()
return self:isState(StateId.attack)
end
--冰冻
function Soldier:onFrozen()
self:toggleState(StateId.frozen)
end
--冰冻结束
function Soldier:onFrozenEnd()
self:onIdle()
end
--是否是冰冻状态
function Soldier:isFrozen()
return self:isState(StateId.frozen)
end
--眩晕
function Soldier:onVertigo()
self:toggleState(StateId.vertigo)
end
--眩晕结束
function Soldier:onVertigoEnd()
self:onIdle()
end
--是否是眩晕状态
function Soldier:isVertigo()
return self:isState(StateId.vertigo)
end
--中毒
function Soldier:onPoisoning()
self:toggleState(StateId.poisoning)
end
--中毒结束
function Soldier:onPoisoningEnd()
self:removeSubState(StateId.poisoning)
end
--是否是眩晕状态
function Soldier:isPoisoning()
return self:isState(StateId.poisoning)
end
--变羊
function Soldier:onSheep()
self:toggleState(StateId.sheep)
end
--变羊结束
function Soldier:onSheepEnd()
self:removeSubState(StateId.sheep)
end
--是否是羊状态
function Soldier:isSheep()
return self:isState(StateId.sheep)
end
--网捕
function Soldier:onNetCatch()
self:toggleState(StateId.netCatch)
end
--网捕结束
function Soldier:onNetCatchEnd()
self:removeSubState(StateId.netCatch)
end
--是否是网捕状态
function Soldier:isNetCatch()
return self:isState(StateId.netCatch)
end
--切换状态
function Soldier:toggleState(iStateId)
self.stateMachine:toggleState(iStateId)
end
--移除子状态
function Soldier:removeSubState(iStateId)
self.stateMachine:removeSubState(iStateId)
end
function Soldier:isState(iStateId)
return self.stateMachine:isState(iStateId)
end
--iMutexSubStateId:互斥的子状态id
function Soldier:toggleStateFail(iCurrentStateId, iNewStateId, iMutexSubStateId)
if iMutexSubStateId == nil then
print("无法从状态 ( " .. StateId.name[iCurrentStateId] .. " ) 切换到状态 ( " .. StateId.name[iNewStateId] .. " )")
else
print("无法从状态 ( " .. StateId.name[iCurrentStateId] .. " ) 切换到状态 ( " .. StateId.name[iNewStateId] .. " ),有互斥状态 ( " .. StateId.name[iMutexSubStateId] .. " )")
end
end
function Soldier:addSubStateFail(iSubStateId)
print("无法新增子状态 ( " .. iSubStateId .. " )")
end
return Soldier
高层模块的调用,跟上一个版本一样,基本没什么改变,好的设计要对外保持调用不变或者少变,将细节的部分封装在内部
function MainScene:registerKeyPad6()
self:setKeypadEnabled(true)
self:addNodeEventListener(cc.KEYPAD_EVENT, function(event)
self:keyPadCallBack6(event)
end)
end
function MainScene:keyPadCallBack6(event)
local iKeyCode = event.code
if iKeyCode == 124 then --A,攻击
self.soldier:onAttack()
elseif iKeyCode == 132 then --I,待机
self.soldier:onIdle()
elseif iKeyCode == 146 then --W,行走
self.soldier:onWalk()
elseif iKeyCode == 129 then --F,冰冻
self.soldier:onFrozen()
elseif iKeyCode == 130 then --G,冰冻结束
self.soldier:onFrozenEnd()
elseif iKeyCode == 145 then --V,眩晕
self.soldier:onVertigo()
elseif iKeyCode == 142 then --S,变羊
self.soldier:onSheep()
elseif iKeyCode == 137 then --N,网捕
self.soldier:onNetCatch()
elseif iKeyCode == 139 then --P,中毒
self.soldier:onPoisoning()
end
end
function MainScene:edition6()
self:registerKeyPad6()
local Soldier = require("app.edition6.soldier.Soldier")
self.soldier = Soldier.new()
local stateMachine = self:createStateMachine1()
self.soldier:bindStateMachine(stateMachine)
end
function MainScene:createStateMachine1()
local StateCreator = require("app.edition6.state.StateCreator")
local StateMachine = require("app.edition6.stateMachine.StateMachine")
local StateRule = require("app.edition6.stateRule.StateRule")
local StateRuleConfig = require("app.edition6.config.StateRuleConfig")
local stateRule = StateRule.new()
stateRule:bindStateRuleConfig(StateRuleConfig)
local stateCreator = StateCreator.new()
local stateMachine = StateMachine.new()
stateMachine:bindStateRule(stateRule)
stateMachine:bindStateCreator(stateCreator)
return stateMachine
end
下一篇文章会开发demo工程,不用安装环境应该就可以跑起来,到此为止,我们已经
实现一个相对高级一点的状态机,能够实现角色的多个状态,比较高的扩展性,新加一个
状态只需要新加一个具体状态类,继承主状态基类或子状态基类,各个状态的实现是独立的,
状态切换规则也提取出来,可以根据不同的需求绑定不同的规则,这里只能说相对高级一点,
为什么,下一篇再说