前言
上一个章节,我们已经了解了什么是状态模式,并设计实现了状态模式。本节我们主要来完成状态模式的实际应用。
我们知道,基于Unity3D开发的游戏都只会再一个场景中运行,因此我们可以把每一个场景都当作一个状态类,并维护这个类。这样场景之间的跳转就可以看作是状态之间的跳转;而且同一时间只会有一个状态存在,也符合游戏执行时只有一个场景存在的实际情况。
场景状态类SceneState的设计
对于游戏大致可分为开始场景、主画面场景和打斗场景,因此可分别对于三个不同的状态。让三个场景类都继承ISceneState,然后再设计一个拥有者SceneStateController,而我们此次主要基于Unity3D来学习设计模式,因此我们还需要一个GameLoop类,让其拥有SceneStateController,以便完成与Unity3D的互动对接,结构如下图所示:
说明如下:
ISceneSate:场景类的接口,主要用于规范场景状态的行为;
StartState、MainMenuState、BattleState:分别对应开始场景、主画面场景和打斗场景;
SceneStateController:场景状态的拥有者,保持当前游戏状态,并控制场景状态的转变,而且负责与GameLoop互动操作;
GameLoop:游戏的主循环,负责与Unity3D互动,并且可以初始化游戏和定期更新游戏。
场景状态模式的实现
ISceneState接口类实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| public class ISceneState { // 状态名称 private string m_StateName = "ISceneState"; public string StateName { get { return m_StateName; } set { m_StateName = value; } }
// 状态控制者 protected SceneStateController m_Controller = null;
public ISceneState(SceneStateController controller) { m_Controller = controller; }
// 状态开始,可初始化一些数据 public virtual void StateBegin() { }
// 状态更新,可以做游戏定时更新输入等 public virtual void StateUpdate() { }
// 状态结束,可做资源释放 public virtual void StateEnd() { } }
|
StateBegin、StateUpdate和StateEnd三个方法分别完成每个场景状态的初始化、更新和结束场景状态时一些释放操作。
开始状态类StartState的实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| public class StartState : ISceneState { public StartState(SceneStateController controller) : base(controller) { StateName = "StartState"; }
// 开始 public override void StateBegin() { //可以在此进行游戏数据加载和初始化等 }
// 更新 public override void StateUpdate() { //更新状态 m_Controller.SetState(new MainMenuState(m_Controller), "MainMenuScene"); } }
|
开始场景主要完成一些游戏数据的加载,因此可以放在StateBegin方法中执行,而在StateUpdate方法中数据加载完成之后来切换游戏的状态,进入MainMenuState主画面场景。
主画面场景类MainMenuState的实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| public class MainMenuState : ISceneState { public MainMenuState(SceneStateController controller) : base(controller) { StateName = "MainMenuState"; } public override void StateBegin() { Button tempBtn = GameObject.Find("StartGameBtn").GetComponent< Button >(); if (tempBtn) tempBtn.onClick.AddListener(() => OnStartGameBtnClick(tempBtn)); } private void OnStartGameBtnClick(Button button) { m_Controller.SetState(new BattleState(m_Controller), "BattleScene"); } }
|
主画面场景主要用来展示玩家的UI一些操作,本次只是简单的应用案例,因此我们在此场景主要时一个“开始”按钮,用来切换进入打斗场景。因此在StateBegin方法中完成开始按钮的查找和点击事件的初始化,当玩家点击按钮时,状态就会被切换为打斗状态,从而进入打斗场景。
打斗场景BattleState的实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| public class BattleState : ISceneState { public BattleState(SceneStateController controller) : base(controller) { StateName = "BattleState"; }
public override void StateBegin() { //游戏初始化等 }
public override void StateEnd() { //状态的释放或其他资源的释放 }
public override void StateUpdate() { //输入 InputProcess();
//游戏逻辑更新
//游戏是否结束 }
// 游戏输入 private void InputProcess() { //玩家输入程序判断代码...... } }
|
在刚开始的StateBegin方法中完成游戏画面等一些的初始化,然后在StateUpdate方法中完成游戏的输入、游戏逻辑、游戏状态的一些更新,最后如果状态结束就在StateEnd方法中完成一些资源的释放等操作。
状态拥有者SceneStateController的实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
| public class SceneStateController { // 状态 private ISceneState m_State = null;
// 状态是不是刚启动 private bool m_bRunBegin = false;
// 场景加载的进度 private AsyncOperation asyncOperation;
public SceneStateController() { }
// 设置状态 // <param name="state">具体状态的实例</param> // <param name="loadSceneName">场景名称</param> public void SetState(ISceneState state,string loadSceneName) { m_bRunBegin = false;
//加载场景 LoadScene(loadSceneName);
//释放前一个状态 if (m_State != null) m_State.StateEnd();
//更新状态 m_State = state; }
// 载入场景 // <param name="sceneName">场景名称</param> private void LoadScene(string sceneName) { if (string.IsNullOrEmpty(sceneName)) return; //异步加载场景 asyncOperation = SceneManager.LoadSceneAsync(sceneName); }
// 更新 public void StateUpdate() { //Debug.Log(asyncOperation.progress); //场景是否加载完成 if (asyncOperation != null && !asyncOperation.isDone) return;
//通知新的State开始 if(m_State != null && m_bRunBegin == false) { m_bRunBegin = true; m_State.StateBegin(); }
//更新 if (m_State != null) m_State.StateUpdate(); } }
|
SceneStateController拥有一个IScneState字段,用来存储当前的游戏状态;SetState实现场景之间的相互转换,首先加载新场景,然后释放前一个场景并把当前场景设置为新的场景,由于加载场景使用了异步加载,因此在StateUpdate方法中先判断场景是否加载完毕,如果加载完毕则初始化新的场景并更新场景。
游戏主循环GameLoop的实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| public class GameLoop : MonoBehaviour { private SceneStateController sceneStateController = new SceneStateController();
private void Awake() { //切换场景时不销毁 DontDestroyOnLoad(this.gameObject);
//随机数种子 Random.InitState((int)System.DateTime.Now.Ticks); }
private void Start() { //设置起始场景 sceneStateController.SetState(new StartState(sceneStateController), ""); }
// Update is called once per frame void Update() { //更新 sceneStateController.StateUpdate(); } }
|
GameLoop中定义并初始化SceneStateController对象,在Start方法中初始化游戏场景状态,在Update方法中定时更新游戏状态。
游戏结果在此就不放出了,有兴趣的可自行实现并体验,下面会给出具体的项目工程地址。
总结
在此状态模式的应用也就讲完了,但为什么我们要使用状态模式呢?
首先,状态模式可以降低维护的难度:当有新的状态增加时,我们只要实现具体的状态,并完成状态的调用就好;也可以减少switch的使用。
其次,状态执行环境单一化:因为每一个场景状态类已经完成了对一个状态的操作相关工作,因此可以清晰了解一个状态执行所需要的资源。
最后我们再回到设计模式的最初目的,就是减少代码的重写,提高复用;因此使用状态模式可以在项目之间共享场景。
那么状态模式还有哪些应用场景?动画有限状态机、NPC角色AI和服务器连接状态等。
参考
<<设计模式与游戏完美开发>>
项目工程地址:基于Unity的状态模式实现
如有侵权,请联系删除