游戏编程模式 - 状态模式
自然界中 状态
属于物体本身。猫咪正在睡觉、吃、喝水,这些动作都属于猫咪自己,它通过自己的大脑调整改变自己每刻的状态。
游戏世界中,每个物体也是如此。他们都有一系列状态,通过状态之间的切换,去描述本身的行为。所以,游戏中每个物体都可能都会存在大量的状态,在编程过程中,我们也需要控制物体不同状态间的切换。
看看设计需求
在某一款游戏中,存在这样一个角色: 它能够走、跑、跳跃和滑行四个状态。通常情况下我们控制这个角色的状态切换,会使用条件语句,如下:
|
|
上面的代码某些情况下会出现一些问题:
- 在上面的代码中,我们将角色的行为和输入控制器绑定在了一起,角色的状态改变全听从用户的输入,但有些时候角色也需要自己改变自己的状态,而不需要外部干预。比如:角色执行完 "滑倒" 动作越过障碍之后,需要自己立马切换回 'Run' 的状态。
因此一个好的 "角色" 设计应该是它的状态既可以对外被更新,也可以对内被更新,控制权由设计者决定。
- 每次输入对应着一个状态的切换,往往一个角色执行一个状态时会需要一定时间,比如说
滑倒
动作,角色可能需要播放一个滑倒的动画。但此时若紧接着按下Space
按键,那么角色就处于 "滑倒式跳跃" 的动画中,是不是很怪异。有问题就有解决方案,加入一个控制变量控制某个状态执行的时候不能执行其他状态,如下面的代码:
|
|
通过加入一个控制变量,问题确实得到了解决。但是,随着状态的增多,你的控制变量也能会随之增多。条件语句嵌套会更加 "彻底"。这样不好,代码不易维护扩展也不够优雅。
也许状态模式就是为这些扰人的需求情况而诞生的吧!
定义
《游戏编程模式》一书中这样定义状态模式:
允许对象在当内部状态改变时改变其行为,就好像此对象改变了自己的类一样。状态模式属于一种行为模式。
实现
- 引入抽象类或接口,声明状态类基本该有的功能
|
|
- 实现具体状态类,继承或实现状态抽象类和接口,实现当前具体状态类需要实现的行为
|
|
JumpState
类的 Handle
方法中,实现了 "跳跃" 状态所需做的事情(也许是一个动画,或者是播放一段音效)。动作完成之后,由 "跳跃" 态进入到 "Idle" 态。状态的切换隐匿在了这个状态类内部,改变的是 _player
所拥有的 State
变量;当切换成功后又会开始执行下一个状态所处的行为(另一个实现了 State
接口的具体状态类的 Handle
方法被执行)。
不仅仅是自身主动切换状态,我们还可以在 Handle
方法内部根据玩家的输入,来控制状态切换:
|
|
上面是 IdleState
类(源码)的 Handle
方法。可以看到,我们将玩家的输入也添加了进来,从而玩家可以主动控制状态切换,也就是状态 "对外" 可被更新。
玩家的在具体状态类中对状态的更新,主要是通过 "角色" 类,毕竟这才是玩家看的到的。紧接着,就来看看角色类吧。
- 状态的拥有者(角色)
|
|
可以看出,角色直接面向玩家。角色拥有状态,并且掌控者玩家的输入 "大权"。状态被改变,同时角色行为也就发生改变,角色持有的是 State
接口类变量,它不需要知道自己究竟需要切换到 "哪个状态",但它知道自己需要切换 "状态",同时它自己也定义了一些列状态的实现供状态类帮它执行。在不同状态类内部主动切换至其它状态(或是响应输入而切换),切换后的状态也可以切换去任意其它所定义的状态类。状态属于物体,就像我们的实现中,状态属于 Player
类,所有的状态切换都服务于它。
- 带有控制变量的状态类
|
|
在原来 JumpState
中我们增加了 _sIsJumping
用来保证 "跳跃" 状态不被重复执行(处于 "跳跃" 状态不能再跳跃)。
多状态之间切换控制问题,最初我们为了控制某个状态 "不变形",而加入了很多控制变量。后面随着状态增多而增多了控制变量,导致代码难以维护。现在引入了状态模式后,我们可以为每个具体状态类引入对应的控制变量,这样无论是对于以后的需求扩展还是代码得维护都会变得更加容易。而每个单独的状态类都能够限制此个状态下能否响应哪些输入、能切换哪些状态,这正是我们需要的!
同时,新增状态类也就意味着你的项目中的具体状态类可能无限增长(属于行为模式的设计模式好像都无法绕过这一点,因为设计初衷就是为此而生);在我们的实现中,所有的状态切换都是先 new
出来新状态类再切换,这有可能导致对象数量增多(对于不具有垃圾回收的语言,切记做好内存释放工作),不过也可以对相同的状态实现状态类实例共享来减少 GC。
看完上面关于状态模式的定义、实现介绍等,感觉似曾相识。多个状态间切换,物体始终处于某一态,等待着某些指令进入下一态,这不就是状态机吗? 是的,这就是一个简单的有限状态机。状态模式将状态切换内部封装,状态自己拥有自己的控制变量从而和外部解耦;由于所有的具体状态类实现了通用状态接口且对象持有接口类,对象不需要关注下一步我是哪个状态,只需要执行状态就行;对于新增状态只需要实现接口并做好对象状态切换工作即可。
参考
- 《游戏编程模式》
- Indienova - 状态模式、有限状态机与 Unity版本实现