遊戲開發中的設計模式——1.狀態模式
這篇博文我記錄了狀態模式在遊戲開發中的簡單應用,用於與切換各個遊戲場景。但是在實際的遊戲開發中,本人並不建議使用這種方式切換遊戲場景,因為Unity本來就是基於元件的遊戲引擎。但是,知識學多了有害無益,為了學習而學習嘛。
先上總圖:
上圖中分為兩個模組,第一個是通用模式下的原型程式碼(主要涉及到狀態模式的概念理解和原型程式碼實現,不做詳細講解哈),第二個是在遊戲開發中的實際應用,三個場景分別對應三個狀態,使用狀態模式切換各個遊戲場景(狀態)。
1.先簡單給出狀態模式在通用模式下的原型程式碼:
在Unity中新建一個場景,把下面的指令碼掛載到任意一個遊戲物件上即可
using UnityEngine;
using System.Collections;
public class StateDesignMood : MonoBehaviour {
// Use this for initialization
void Start () {
Context context = new Context();
context.SetState(new ConcreteStateA(context));
context.Handle(5);
context.Handle(20);
context.Handle(30);
context.Handle(4 );
context.Handle(50);
}
}
///1. 概述:當一個物件的內在狀態改變時允許改變其行為,這個物件看起來像是改變了其類。
/// 2. 解決的問題:主要解決的是當控制一個物件狀態轉換的條件表示式過於複雜時的情況。把狀態的判斷邏輯轉移到表示不同的一系列類當中,可以把複雜的邏輯判斷簡單化。
/// <summary>
/// 上下文環境類:它定義了客戶程式需要的介面並維護一個具體狀態角色的例項,將與狀態相關的操作委託給當前的Concrete State物件來處理。
/// 就好比該類是遊戲中的一個主角,他需要進行各種不同的狀態
/// </summary>
public class Context {
private IState state;
public void SetState(IState state) {
this.state = state;
}
public void Handle(int arg) {
state.Handle(arg);
}
}
/// <summary>
/// 抽象狀態介面(State):定義一個介面以封裝使用上下文環境的的一個特定狀態相關的行為。
/// </summary>
public interface IState {
void Handle(int arg);
}
/// <summary>
/// 具體狀態類A(Concrete StateA):實現抽象狀態定義的介面
/// </summary>
public class ConcreteStateA : IState
{
private Context context;
public ConcreteStateA(Context context)
{
this.context = context;
}
public void Handle(int arg)
{
Debug.Log("我是具體狀態類A," + arg);
if (arg > 10)//如果傳入的引數大於10就轉向另一個具體狀態B處理
{
this.context.SetState(new ConcreteStateB(this.context));
}
}
}
/// <summary>
/// 具體狀態類B(Concrete StateB):實現抽象狀態定義的介面
/// </summary>
public class ConcreteStateB : IState
{
private Context context;
public ConcreteStateB(Context context)
{
this.context = context;
}
public void Handle(int arg)
{
Debug.Log("我是具體狀態類B," + arg);
if (arg <= 10)//如果傳入的引數大於10就轉向另一個具體狀態A處理
{
this.context.SetState(new ConcreteStateA(this.context));
}
}
}
2.在這個遊戲開發中,狀態對應的就是場景,有3個遊戲場景:開始場景,選單場景,戰鬥場景,這些場景只是一個簡單的雛形,沒有實現其具體的功能,因為我們這個模式的主題內容不在這,而在於使用狀態模式實現場景的切換。
三個場景:
六個指令碼(具體功能下面講解中體現):
指令碼UML圖(重點理解,可參照普通模式下原型程式碼的UML圖加深理解):
GameLoop指令碼用於啟動整個專案:
using UnityEngine;
using System.Collections;
/// <summary>
/// 此類用來啟動狀態模式下的場景管理器
/// </summary>
public class GameLoop : MonoBehaviour {
private SceneStateController stateController = null;
private void Awake()
{
DontDestroyOnLoad(this.gameObject);
}
// Use this for initialization
void Start () {
stateController = new SceneStateController();
stateController.SetState(new StartState("Start", stateController), false);
}
// Update is called once per frame
void Update () {
stateController.StateUpdate();//啟動場景管理器
}
}
SceneStateController用於控制各個狀態(場景)的切換:
using UnityEngine;
using System.Collections;
using UnityEngine.SceneManagement;
/// <summary>
/// 場景管理器
/// </summary>
public class SceneStateController {
private ISceneState sceneState;//申明一個ISceneState物件
private AsyncOperation ao;//用於接收非同步載入場景的返回值, 判斷非同步載入場景是否載入完畢
private bool isRunStart = false;//是否執行過StateStart( )方法,確保StateStart( )只調用一次
/// <summary>
/// //設定狀態方法
/// </summary>
/// <param name="sceneState">場景狀態介面物件</param>
/// <param name="isLoadScene">是否需要載入場景(預設第一個場景不用載入)</param>
public void SetState(ISceneState sceneState,bool isLoadScene = true) {
if (sceneState != null)
{
sceneState.StateEnd();//該場景結束後做一下清理工作啥的
}
this.sceneState = sceneState;//將該狀態(該場景)更新為新狀態(新場景)
if (isLoadScene == true)
{
ao = SceneManager.LoadSceneAsync(sceneState.SceneName);
isRunStart = false;
}
else
{
this.sceneState.StateStart();
isRunStart = true;
}
}
/// <summary>
///更新狀態(場景)方法
/// </summary>
public void StateUpdate() {
if (ao != null && ao.isDone == false) return;//如果處於正在載入的過程中,就不用更新狀態了
if (ao != null && ao.isDone == true && isRunStart == false)//如果非同步載入完成,並且沒有執行過StateStart()方法就執行載入資源啥的方法
{
sceneState.StateStart();
isRunStart = true;
}
if (sceneState != null)
{
sceneState.StateUpDate();
}
}
}
接下來就是具體狀態模式的程式碼來實現器切換功能了具體實現步驟在上面總圖中有說明,總共分為7步。
2.1 建立場景狀態的基礎介面:
using UnityEngine;
using System.Collections;
/// <summary>
/// 場景狀態介面(嚴格意義上來說不是一個介面,此時是一個父類,因為在其中會有子類需要繼承的方法)
/// </summary>
public class ISceneState {
private string sceneName;//需要載入的場景名
protected SceneStateController sceneController;//表示該狀態的擁有者,也就是場景管理器
/// <summary>
/// 獲取場景名
/// </summary>
public string SceneName
{
get
{
return sceneName;
}
}
/// <summary>
/// 構造方法
/// </summary>
/// <param name="sceneName">場景名</param>
/// <param name="sceneController">場景管理器</param>
public ISceneState(string sceneName,SceneStateController sceneController) {
this.sceneName = sceneName;
this.sceneController = sceneController;
}
/// <summary>
/// 場景載入時的方法(在遊戲中可能需要載入資源啥的),每次進入到這個狀態時呼叫
/// </summary>
public virtual void StateStart() { }
/// <summary>
/// 場景結束時的方法(在遊戲中可能需要釋放資源啥的),每次退出到這個狀態時呼叫
/// </summary>
public virtual void StateEnd() { }
/// <summary>
/// 更新場景
/// </summary>
public virtual void StateUpDate() { }
}
2.2 建立場景狀態的三個子類(開始介面,選單介面,戰鬥介面):
using UnityEngine;
using System.Collections;
using UnityEngine.UI;
/// <summary>
/// 開始狀態(開始介面)
/// </summary>
public class StartState : ISceneState
{
/// <summary>
/// 開始介面(狀態)構造方法
/// </summary>
/// <param name="sceneName">場景名</param>
/// <param name="sceneController">場景控制器</param>
public StartState(string sceneName, SceneStateController sceneController) : base(sceneName, sceneController) { }
private Image logo;//開始介面Logo
private float smoothingSpeed = 2f;//透明度過度平滑速率
private float waitTime = 3f;//載入下一個介面的等待時間
public override void StateStart()
{
logo = GameObject.Find("Logo").GetComponent<Image>();
logo.color = new Color(1,1,1,0);
}
public override void StateUpDate()
{
logo.color = Color.Lerp(logo.color,new Color(1,1,1,1),Time.deltaTime * smoothingSpeed);//設定開始介面UI透明度漸變
waitTime -= Time.deltaTime;
if (waitTime <= 0)
{
sceneController.SetState(new MainMenuState("MainMenu", sceneController));//到達等待時間進入下一個場景
}
}
}
using UnityEngine;
using System.Collections;
using UnityEngine.UI;
/// <summary>
///第二狀態(主場景)
/// </summary>
public class MainMenuState : ISceneState
{
/// <summary>
/// 開始介面(狀態)構造方法
/// </summary>
/// <param name="sceneName">場景名</param>
/// <param name="sceneController">場景控制器</param>
public MainMenuState(string sceneName, SceneStateController sceneController) : base(sceneName, sceneController){}
private Button startButton;//開始遊戲按鈕
public override void StateStart()
{
startButton = GameObject.Find("StartButton").GetComponent<Button>();
startButton.onClick.AddListener(OnStartBtnDown);//新增開始遊戲按鈕事件監聽
}
private void OnStartBtnDown() {//開始遊戲按鈕事件
sceneController.SetState(new FightState("FightScene", sceneController));//設定為戰鬥狀態(戰鬥場景)
}
}
using UnityEngine;
using System.Collections;
using UnityEngine.UI;
/// <summary>
/// 第三狀態(戰鬥場景)
/// </summary>
public class FightState : ISceneState
{
/// <summary>
/// 開始介面(狀態)構造方法
/// </summary>
/// <param name="sceneName">場景名</param>
/// <param name="sceneController">場景控制器</param>
public FightState(string sceneName, SceneStateController sceneController) : base(sceneName, sceneController){}
private Button returnMenuBtn;//返回按鈕
public override void StateStart()
{
returnMenuBtn = GameObject.Find("ReturnMenuButton").GetComponent<Button>();
returnMenuBtn.onClick.AddListener(OnReturnMenuBtnDown); //新增返回主選單按鈕事件監聽
}
private void OnReturnMenuBtnDown() {//返回按鈕點選事件
sceneController.SetState(new MainMenuState("MainMenu", sceneController));//設定場景為主選單場景
}
}
2.3 場景狀態模式的UML圖(上面已經給出,不在贅述)
2.4 開發狀態切換(場景切換)功能:
程式碼在SceneStateController場景管理器類中實現的。
2.5 控制開始場景的動畫播放:
既然是開始場景的動畫播放,那就是處於開始場景狀態中該做的事情,所以具體程式碼實現在StartState類中(也不過就是一個Color.Lerp()方法而已)。
2.6 由開始狀態切換到主選單狀態:
設計是當開始動畫播放完後就會進入主選單場景,所以具體程式碼也在StartState中體現。
2.7 設計主選單介面和戰鬥場景的切換:
主要是在主選單介面點選開始遊戲按鈕然後就進入戰鬥場景。所以在主選單介面繫結開始遊戲按鈕的點選事件,點選進入戰鬥場景即可。