Unity多媒体互动开发,Unity最新技术学习

设计模式与游戏完美开发学习--状态模式(应用篇)

前言

  上一个章节,我们已经了解了什么是状态模式,并设计实现了状态模式。本节我们主要来完成状态模式的实际应用。
  我们知道,基于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的状态模式实现
如有侵权,请联系删除

-------------本文结束感谢您的阅读-------------