状态模式C#版

2019-04-14 20:06发布

目录 例子 普通方式 实现 问题 解决方式 枚举方式 实现 问题 状态模式 状态接口 状态对象 游戏玩家

作为设计模式中的一种,状态模式在软件、游戏设计中有很重要的作用。为了理解这种模式,本文首先用普通方式实现了一个例子,然后分别用枚举方式和状态模式实现了这个例子,所有的代码经过测试可以在Unity(2017.2)中运行。
 

例子

游戏中有一个处于站立姿态的女英雄,按“↑”时会跳跃;按住“”时,会卧倒,松开时则会恢复站立;在跳跃姿态按“↓”时会下斩
 

普通方式

实现

 不考虑状态模式,我们分别实现这三个状态及其过渡。 using UnityEngine; public class HeroBasic : MonoBehaviour { private void Update() { //站立到跳跃 if(Input.GetKeyDown(KeyCode.UpArrow)) { print("jump"); } //站立到卧倒 else if(Input.GetKeyDown(KeyCode.DownArrow)) { print("duck"); } //卧倒到站立 else if(Input.GetKeyUp(KeyCode.DownArrow)) { print("stand"); } } }

问题

  1. 一直按“↑”,会不断跳跃,产生悬空效果;
  2. 在跳跃姿态下按“↓”,会立即卧倒,同时在卧倒状态下按“↑”,会立即跳跃;
  3. 无法做出下斩动作;

解决方式

引入两个字段分别标志是否跳跃和卧倒,程序看起来如下。 using UnityEngine; public class HeroBasic : MonoBehaviour { private bool isJumping = false; private bool isDucking = false; private void Update() { //站立到跳跃 if(Input.GetKeyDown(KeyCode.UpArrow)) { if (!isJumping && !isDucking) { print("jump"); isJumping = true; } } else if(Input.GetKeyDown(KeyCode.DownArrow)) { //站立到卧倒 if (!isJumping && !isDucking) { print("duck"); isDucking = true; } //下斩 if(isJumping) { isJumping=false; print("dive"); } } //卧倒到站立 else if(Input.GetKeyUp(KeyCode.DownArrow)) { if(isDucking) { print("stand"); isDucking = false; } } } } 每次添加一个新的状态过渡时,我们不得不添加一个新的字段,并且需要在源代码中各个位置进行修改;另外,逻辑判断复杂,因为isJumping和isDucking并不会同时为真。  

枚举方式

实现

我们用枚举数组替代之前的布尔字段,从而得到有限状态机形式的实现。 using UnityEngine; public enum HeroState { Stand, Jump, Duck, Dive } public class HeroEnum : MonoBehaviour { private HeroState state; private void Update() { switch(state) { case HeroState.Stand: if(Input.GetKeyDown(KeyCode.UpArrow)) { state = HeroState.Jump; print("jump"); } else if(Input.GetKeyDown(KeyCode.DownArrow)) { state = HeroState.Duck; print("duck"); } break; case HeroState.Jump: if(Input.GetKeyDown(KeyCode.DownArrow)) { state = HeroState.Dive; print("dive"); } break; case HeroState.Duck: if(Input.GetKeyUp(KeyCode.DownArrow)) { state = HeroState.Stand; print("stand"); } break; } } }

问题

此时,如果需要增加一个蓄能动作,角 {MOD}在俯卧时可以蓄能,蓄能满时可以释放一个特殊攻击,这样,我们需要增加另外一个字段来存储蓄能时间chargeTime。 using UnityEngine; public enum HeroState { Stand, Jump, Duck, Dive } public class HeroEnum : MonoBehaviour { private HeroState state; private int chargeTime = 0; public int MaxTime = 100; private void Update() { switch (state) { case HeroState.Stand: if (Input.GetKeyDown(KeyCode.UpArrow)) { state = HeroState.Jump; print("jump"); } else if (Input.GetKeyDown(KeyCode.DownArrow)) { state = HeroState.Duck; print("duck"); } chargeTime = 0; break; case HeroState.Jump: if (Input.GetKeyDown(KeyCode.DownArrow)) { state = HeroState.Dive; print("dive"); } chargeTime = 0; break; case HeroState.Duck: if (Input.GetKeyUp(KeyCode.DownArrow)) { state = HeroState.Stand; print("stand"); } chargeTime ++; if(chargeTime > MaxTime) { chargeTime = 0; print("super"); } break; } } } 这样,又出现了普通方式一样的问题,程序修改变得混乱不堪,纠其原因,角 {MOD}状态和角 {MOD}本身耦合性太高,如果需要新增一个状态,必然会使角 {MOD}本身进行修改。于是,我们需要一种新的思维方式来构建我们的程序,这种思维方式应该可以解耦角 {MOD}状态和角 {MOD}本身,可以让角 {MOD}状态内部改变角 {MOD}本身状态,从而要新增一个角 {MOD}状态,只需要修改角 {MOD}状态,而不需要修改角 {MOD}本身。  

状态模式

状态模式被GoF这样描述:
允许一个对象在其内部状态发生改变时改变自己的行为,看起来就像是修改了本身的类型

状态接口

为所有的状态定义一个接口,在该接口中定义了所有状态类必须实现的方法。 public interface IHeroState { void HandleInput(Hero hero); }

状态对象

为所有状态定义一个具体的类,继承状态接口,并实现状态接口中的方法。 //站立状态 using UnityEngine; using System.Threading; public class IdleState : IHeroState { public void HandleInput(Hero hero) { if (Input.GetKeyDown(KeyCode.DownArrow)) { hero.printMessage("duck"); hero.State = new DuckingState(); } if (Input.GetKeyDown(KeyCode.UpArrow)) { hero.printMessage("jump"); hero.State = new JumpingState(); Thread waitForSeconds = new Thread(WaitingSeconds); waitForSeconds.Start(hero); } } void WaitingSeconds(object hero) { Thread.Sleep(1000); Hero heroo = (Hero)hero; heroo.printMessage("stand"); heroo.State = this; } } //下斩状态 public class DiveState : IHeroState { public void HandleInput(Hero hero) { } } //跳起状态 using UnityEngine; using System.Threading; public class JumpingState : IHeroState { public void HandleInput(Hero hero) { if(Input.GetKeyDown(KeyCode.DownArrow)) { hero.printMessage("dive"); hero.State = new DiveState(); Thread waitForSeconds = new Thread(WaitingSeconds); waitForSeconds.Start(hero); } } void WaitingSeconds(object hero) { Thread.Sleep(1000); Hero heroo = (Hero)hero; heroo.printMessage("stand"); heroo.State = this; } } //下蹲状态 public class DiveState : IHeroState { public void HandleInput(Hero hero) { } }  

游戏玩家

最后,在游戏玩家中定义一个状态变量,该状态变量不再是之前的枚举类型,而是状态接口类型。 using UnityEngine; public class Hero : MonoBehaviour { public IHeroState State; private void Start() { State = new IdleState(); } private void Update() { HandleInput(); } void HandleInput() { State.HandleInput(this); } public void printMessage(string s) { print(s); } }  这样我们就实现了状态模式。