一、前言
在有多个状态用来回切换的时候我们可以使用状态模式
以书为例,讲的是3种状态,开始游戏场景,主界面场景,游戏战斗场景
我们在开发游戏时对这三种状态要来回加载,因此适用于状态模式
使用状态模式可以避免使用Switch结构,也就避免了游戏中频繁添加内容就要修改switch代码的操作
因此遵守了开放封闭原则:
代码重构时尽量去添加代码,而不是修改代码的设计原则
二、类的介绍
- 首先是状态基类,这个状态基类就是游戏场景,主界面场景它们的父类,同时也是一个抽象类,需要定义3种(开始,结束,保持状态)抽象方法给子类重写,并且可以有一个状态控制者的类对象引用。做一些其他操作
- 有基类就肯定有子类了,子类就是具体的游戏场景,主界面场景,游戏战斗状态场景类了。他们需要重写父类的虚方法来做具体实现。
- 状态控制者类,因为我们一次之能使用一种状态。且需要来回切换。因此一定需要一个状态类来对他们进行切换。
三、学习疑问
上面只是做了个大致了解,但是还是不会写。解决以下的疑问试试看
类如何初始化他们呢?
实际该如何调用他们的方法呢?
如何进行状态之间的转换呢?
三种状态方法可以做什么样的事呢?
1、类如何初始化他们呢?
我们应该要知道,一个类有一个对象,但是一个类里又有包含着其他类的情况。(关联关系),他们到底该如何初始化?
首先看状态控制类: 状态控制类有一个状态类对象,但是这个状态类成员是被私有的,那该如何给这个类成员赋值状态类对象呢?
那么,应该理解这个状态类对象,这个状态类对象就是可以进行来回设置的,因此需要一个公有方法给状态类对象行赋值(状态类的子类对象)。而这个状态类有3种状态方法,此时就可以在状态控制类中做不同的操作了。
因此:可通过公有方法来获取这个状态类对象,来操作这个状态类成员,进而可以操作各种状态方法。
这里展示状态控制类代码
using UnityEngine;
using System.Collections;
/// <summary>
/// Scene state ctrl.
/// 机器类: 用来控制各个状态(SceneBase)
/// </summary>
public class SceneStateCtrl{
//当前状态
public SceneBase _currentState;
//场景是否加载完
private bool _isLoading = false;
//状态切换
public void SetState(SceneBase newState,string sceneName){
_isLoading = false;
if (_currentState != null) {
//先让当前场景退出,再进行场景切换
_currentState.OnExit ();
}
//场景切换
loadScene (sceneName);
_currentState = newState;
}
private void loadScene(string sceneName) {
//因为场景切换涉及新场景的资源加载,所以可能比较慢
//如果已经点击切换,就屏蔽掉再次点击
if (!_isLoading) {
_isLoading = true;
if (sceneName != "") {
Application.LoadLevel (sceneName);
}
}
}
public void UpdateState(){
//只有当场景加载完成,才能update,并且此时状态是一直持续的
if (Application.isLoadingLevel || _currentState == null)
return;
if (_isLoading) {
_currentState.OnEnter ();
_isLoading = false;//因此在切换状态的时候要更新设置为 false
}
if (_currentState != null) {
_currentState.OnUpdate ();
}
}
}
可以看出:
1. 状态场景类的状态方法都是公有的方法,被状态控制类中的状态类对象调用。
2. 而当前状态类对象在控制类的公有方法SetState中获取
3. 通过new创建状态控制类对象,修改状态类对象,状态基类对象通过状态控制者的SetState(new ())方法来创建,每次创建都new一个子类状态对象
之后看状态基类: 状态基类,状态基类有一个被保护的状态控制者类成员,这个控制者类对象是通过(外界调用)构造函数来给这个状态控制类赋值对象的。此后子类可以使用这个控制者类对象可以调用SetState方法,进行控制下一个状态场景。
2、实际该如何调用他们的方法呢?
类的公有方法的。他们之间的调用关系该是怎么样的呢?
类内部没有自己的对象,所有大部分公有方法都是外部类创建对象,在外部类调用对象的公有方法
3、场景之间的状态该如何转换?
通过点击按钮切换状态,如开始按钮,结束按钮,复杂点就是菜单栏上的开始结束按钮
4、3种状态方法(开始、结束、保持)可以做什么样的事?
因为他们都没有继承MonoBehaviour,没有Start Update等方法
因此在开始状态方法OnEnter(){}可以做状态初始化,获取组件,对象,声明事件检测(按钮点击)
在保持方法OnUpdate(){}可以做持续的if判断,持续性的内容
最后离开当前状态的OnExit()方法,该方法必须被调用,因此可以做当前状态场景切换后,新场景切换之前的事
列出状态场景基类,以及状态场景子类代码:
using UnityEngine;
using System.Collections;
/// <summary>
/// Scene base.
/// </summary>
public class SceneBase{
protected SceneStateCtrl stateCtrl = null;
public const string Name = "";
public SceneBase(SceneStateCtrl stateCtrler){
this.stateCtrl = stateCtrler;
}
public virtual void OnEnter(){}
public virtual void OnUpdate(){}
public virtual void OnExit(){}
}
开始场景类
using UnityEngine;
using System.Collections;
/// <summary>
/// Start scene state.
/// 场景中的工作
/// 例子:找到场景中的按钮,给按钮添加事件,触发事件,改变状态
/// </summary>
public class StartSceneState : SceneBase{
public const string Name = "StartScene";
public StartSceneState(SceneStateCtrl stateCtrler):base(stateCtrler){
}
private UIButton _firstBtn;
public override void OnEnter ()
{
base.OnEnter ();
_firstBtn = UITool.FindUI<UIButton> ("StartBtn");
UIEventListener.Get (_firstBtn.gameObject).onClick = FirstBtnClick;
}
public override void OnUpdate ()
{
base.OnUpdate ();
if (Application.isLoadingLevel || stateCtrl == null) {
return;
}
}
public override void OnExit ()
{
base.OnExit ();
}
void FirstBtnClick(GameObject go){
Debug.Log ("开始游戏");
stateCtrl.SetState (new MainSceneState (stateCtrl),"MainScene");
}
}
四、总结
最后做个抽象的总结
这里使用了一个5个不继承MonoBehaviour的类,分别表示1个状态基类,3个状态子类,1个状态控制者类。然后只使用一个继承MonoBehaviour的游戏循环类。
游戏循环类开始时new一个状态控制者类对象,状态控制类对象则可以开始控制各种子类,做具体的实现方法。
游戏循环类有 Start,Update方法,分别对应(调用)控制类对象的Start(setState初始化),update方法,然后又控制类对象对应(调用)状态子类的OnEnter,OnUpdate方法。
这里看下GameLoop游戏状态循环类
using UnityEngine;
using System.Collections;
public class GameLoop : MonoBehaviour {
private SceneStateCtrl _currentStateCtrl = null;
void Start () {
//切换场景,游戏物体不销毁
DontDestroyOnLoad (this.gameObject);
_currentStateCtrl = new SceneStateCtrl ();
//字符串如此做是不让场景做切换,因为运行程序就在start场景中,
//不需要再次切换到Start场景
_currentStateCtrl.SetState (new StartSceneState (_currentStateCtrl), "");
}
void Update () {
_currentStateCtrl.UpdateState ();
}
}
五、细节上
再说一个细节上的问题,因为每次切换状态都会调用SetState()方法new一个状态子类对象,但是如果重复切换则会new出很多个同样的子类状态对象。此时GC管理内存的功能就体现出来了,它会把智能的把不会再用到的子类状态对象进行释放,因此可以放心的通过方法来new对象。
当然我们还是要少new出对象,因为释放内存同样会降低程序效率