在这之前,先来说下什么是有限状态机(Finite-state machine)
背景
我们在开发游戏,比如rpg游戏时,会涉及到玩家有各种状态,如攻击状态,等待状态等,如果用ifelse也可以实现,但是随着程序复杂度越来越高,这样肯定是不易于代码维护的,很容易出错,而且代码可读性比较差。这时,用状态机就可以很好的解决这些问题。我们可以通过状态机来记录它的各个状态(state)和状态之间的转换(transition),并且设置回调函数来做一些处理。
状态机特征
1.状态总数是有限的。
2.任一时刻,只处在一种状态中。
3.某种条件下,会从一种状态转变到另一种状态。
使用方法
把state-machine.min.js导入为cocoscreator插件,
var turnFsm = new StateMachine({
init:'none',
transitions: [
{ name: 'toStart', from: 'none', to: 'start' },
{ name: 'playerTurn', from: 'start', to: 'player' },
{ name: 'enemyTurn', from: 'player', to: 'enemy' },
{ name: 'finish', from: 'enemy', to: 'end' },
{ name: 'restart', from: 'end', to: 'start' }
],
methods: {
onStart: function() {
console.log("onStart")
turnFsm.playerTurn(); //错误示范,此时调用会报错
},
onPlayer: function(){
console.log("onPlayer")
},
onEnemy: function(){
console.log("onEnemy")
},
onEnd: function(){
console.log("onEnd")
}
}
});
如上,定义了一个状态机,上边的状态机是用来控制回合制游戏每回合双方行动流程的
如从 none -> start开始行动 -> player玩家行动 -> enemy敌人行动 -> end行动结束
1.init为初始状态,设为none,(none是默认的初始状态,其实可以不用定义)
2.trainstions是来描述状态变化规则的数组,每一项是一个对象,
- from:当前行为从哪个状态来
- to:当前行为执行完会过渡到哪个状态
- name:当前行为的名字
3.methods是定义的生命周期方法
方法 | 解释 |
---|---|
onBeforeTransition | 任何动作触发前触发 |
onBefore<TRANSITION> | 在特定动作TRANSITION前触发 |
onTransition | 在任何动作发生期间触发 |
onAfterTransition | 任何动作触发后触发 |
onAfter<TRANSITION> | 在特定动作TRANSITION后触发,可简写为on<TRANSITION> |
onEnterState | 当进入任何状态时触发 |
onEnter<STATE> | 进入一个特定的状态STATE时触发,可简写为on<STATE> |
onLeaveState | 离开任何一个状态的时候触发 |
onLeave<STATE> | 在离开特定状态STATE时触发 |
因此,对应以上定义的方法解释如下:
onStart //当进入start状态触发
onPlayer //当进入player状态触发
onEnemy //当进入enemy状态触发
onEnd //当进入end状态触发
注意:要想改变状态机的状态,就要调用transition方法,如
turnFsm.playerTurn(),才可以从start到player状态。
生命周期顺序
以playerTurn事件为例,从start状态到player状态,生命周期函数的发生顺序为:
onBeforePlayerTurn -> onLeaveStart -> onEnterPlayer -> onAfterPlayerTurn
常用方法
方法 | 解释 |
---|---|
fsm.state | 返回当前的状态 |
fsm.is(s) | 返回bool值,表示当前状态机状态是否为 s |
fsm.can(t) | 返回bool值,表示过渡方法t是否可以从当前状态触发 |
fsm.cannot(t) | 返回bool值,表示过渡方法t是否不能从当前状态触发 |
fsm.transitions() | 返回从当前状态可以过渡到的过渡方法列表 |
fsm.allTransitions() | 返回所有过渡方法的列表 |
fsm.allStates() | 返回状态机所有状态的列表 |
示例:
onStart: function() {
console.log("onStart")
cc.log(turnFsm.state)
cc.log(turnFsm.can("playerTurn"));
cc.log(turnFsm.transitions());
cc.log(turnFsm.allTransitions());
cc.log(turnFsm.allStates());
turnFsm.playerTurn(); //此时调用会报错
},
以上执行结果为:
报错信息
可以看到最后一行turnFsm.playerTurn();会报错
transition is invalid while previous transition is still in progress
报错信息截图如下:
上边报错的意思是此动作不可执行,因为之前的动作还在执行中。
从turnFsm.can(“playerTurn”)这个函数返回false就可以看出此时不能执行这个过渡方法。
解决方法
我们可以设置setTimeOut延时执行我们要执行的方法,这时,状态机会认为上一个transition已经执行完成
setTimeout(function(){
cc.log("after 1s")
cc.log(turnFsm.can("playerTurn")); //此时返回true
turnFsm.playerTurn(); //此时可以正常执行下一个transition
},1000);
可以看到,当延迟一秒之后,turnFsm.can(“playerTurn”)此时返回true,说明可以在这个时候正常调用下一个transition(playerTurn)。
如果还有其他方法可以规避此错误,欢迎补充~