U3D架構系列之- FSM有限狀態機設計六(總結篇)

  由於最近一直趕項目進度,沒時間寫,昨晚終於項目終於完成了,空閒下來,做一個總結。在這一篇中主要是把前五章一起總結一下,以及通過舉例演示如何使用?有限狀態機在遊戲中使用的地方非常多,比如我們界面之間的切換,角色的狀態切換等等。所以非常值得大家去學習一下,接下來我們主要實現的功能,爲了表達清楚,我通過圖例給大家說明一下:

wKiom1TcXfDAJ_Q0AAEVcBwHlNY828.jpg


給大家解析一下,程序運行首先進入主菜單,裏面有三個按鈕,開始遊戲,音量,退出遊戲。先從第一個說起,如果是開始遊戲,它會進入到下一個界面遊戲界面,遊戲界面有個返回主菜單功能。二者可以互相切換。接下來是音量按鈕,整個按鈕是調節音量的,調節好了後,點確認和取消都是返回主菜單。二者之間互相切換,最後一個是退出遊戲,會進入是否退出界面,如果否,返回主界面,如果是真正的關閉遊戲。我們就把這個簡單的功能用我們的有限狀態機實現一下:

首先我們聲明兩個對象:

public static EventSystem.Dispatcher Events = new EventSystem.Dispatcher();
 public FiniteStateMachine FSM = new FiniteStateMachine();

events主要是創建一個全局的事件系統用於我們指定的UI。

FSM是作爲一個狀態機被驅動。

接下來我們註冊幾個狀態用我們的狀態機:

 FSM.Register("MainMenu", new MainMenuUI());
 FSM.Register("AudioMenu", new AudioMenuUI());
 FSM.Register("MainGame", new MainGame(FSM));
 FSM.Register("QuitGame", new QuitGameUI());

我們用EntryPoint告訴玩家我們第一個界面是主界面:

FSM.EntryPoint("MainMenu");

我們爲主界面定義幾個actions,OPEN_AUDIO,PLAY_GAME, QUIT_GAME.其中OPEN_AUDIO和QUIT_GAME用於取代頂部棧的狀態。PLAY_GAME用於增加狀態棧新的item。代碼如下:

FSM.State("MainMenu").On("OPEN_AUDIO").Enter("AudioMenu")
    .On("PLAY_GAME").Push("MainGame")
    .On("QUIT_GAME").Enter("QuitGame");

退出菜單響應PROCESS_QUIT action。代碼如下:

 FSM.State("QuitGame").On("PROCESS_QUIT", delegate(bool sure) {
     if (sure) {
      gameObject.GetComponent<TestUIState>().enabled = false;
      Camera.main.backgroundColor = Color.black;
     } else { FSM.Enter("MainMenu"); }
    });

上述代碼主要實現的功能:如果確認遊戲結束,否則返回主菜單。


遊戲類是負責對於主菜單彈出棧頂元素的。

using UnityEngine;
using System.Collections;

class MainGame : MenuUI, IState {
	protected FiniteStateMachine FSM;

	protected float Score = 0;

	public MainGame(FiniteStateMachine parentMachine) {
		FSM = parentMachine;
	}

	public void OnEnter(string prevState) {
		Score = 0;
	}
	
	public void OnExit(string nextState) {
		
	}
	
	public void OnUpdate() {
		
	}
	
	public override void DoGUI() {
		if (GUILayout.Button("Quit / Back To Menu", GUILayout.Width(Screen.width))) {
			FSM.Pop();
		}
		GUILayout.Space(25);
		GUILayout.Label("The waiting game!");
		GUILayout.Space(25);
		GUILayout.Label("CurrentScore: " + System.Convert.ToInt32(Score));

		Score += Time.deltaTime;
	}
}


聲音菜單保留它自己的狀態,處理音量邏輯。代碼如下:

SM.State("AudioMenu").On("BACK_TO_MENU").Enter("MainMenu");

最後將每一個事件系統掛到狀態機的actions裏面,代碼如下:

Events.On("OpenMainGame", delegate() { FSM.CurrentState.Trigger("PLAY_GAME"); });
   Events.On("OpenAudioMenu", delegate() { FSM.CurrentState.Trigger("OPEN_AUDIO"); });
   Events.On("QuitGame", delegate() { FSM.CurrentState.Trigger("QUIT_GAME"); });
  Events.On("ConfirmQuit", delegate() { FSM.CurrentState.Trigger("PROCESS_QUIT", true); });
   Events.On("CancelQuit", delegate() { FSM.CurrentState.Trigger("PROCESS_QUIT", false); });
  Events.On("BackToMenu", delegate() { FSM.CurrentState.Trigger("BACK_TO_MENU", false); });

大家可能會問,狀態機是如何切換的,我們將在Update裏面實現,代碼很簡單:

public void Update() {
   FSM.Update();
 }

這樣就可以每一幀都可以進行檢測push還是top狀態機了。

爲了響應主界面我們定義了一個OnGUI函數:

void OnGUI() {
   if (FSM.CurrentState == null)
    return;
   MenuUI ui = (MenuUI)FSM.CurrentState.StateObject;
  ui.DoGUI();
 }

最後因爲我們涉及到主界面各個操作,所以它們都有自己的類。

我將它們都拿出來給大家分享:

主菜單中聲音菜單的邏輯代碼如下:

using UnityEngine;
 using System.Collections;
class AudioMenuUI : MenuUI, IState {
 float volume = 0.5f;
 float backupVolume = 0.0f;
 public void OnEnter(string prevState) {
   backupVolume = volume;
 }

 public void OnExit(string nextState) {
 }

 public void OnUpdate() {
   
 }

 public override void DoGUI() {
   GUILayout.Space(25.0f);
   volume = GUILayout.HorizontalSlider(volume, 0.0f, 1.0f, GUILayout.Width(Screen.width));
  GUILayout.BeginHorizontal();
   GUILayout.FlexibleSpace();
   GUILayout.Label("Volume: " + System.Convert.ToInt32(volume * 100.0f) + " %");
   GUILayout.FlexibleSpace();
   GUILayout.EndHorizontal();
  GUILayout.BeginHorizontal();
   if (GUILayout.Button("Cancel", GUILayout.Height(75.0f))) {
    volume = backupVolume;
    TestUIState.Events.Trigger("BackToMenu");
   }
   if (GUILayout.Button("Confirm", GUILayout.Height(75.0f))) {
    TestUIState.Events.Trigger("BackToMenu");
   }
   GUILayout.EndHorizontal();
 }
 }

主菜單類邏輯代碼如下:

using UnityEngine;
 using System.Collections;
public class MainMenuUI : MenuUI, IState {
 public void OnEnter(string prevState) {
   
 }

 public void OnExit(string nextState) {
   
 }

 public void OnUpdate() {
   
 }

 public override void DoGUI() {
   if (GUILayout.Button("Play Game", GUILayout.Width(Screen.width), GUILayout.Height(Screen.height / 3))) {
    TestUIState.Events.Trigger("OpenMainGame");
   }
   
   if (GUILayout.Button("Audio Menu", GUILayout.Width(Screen.width), GUILayout.Height(Screen.height / 3))) {
    TestUIState.Events.Trigger("OpenAudioMenu");
   }
   
   if (GUILayout.Button("Quit Game", GUILayout.Width(Screen.width), GUILayout.Height(Screen.height / 3))) {
    TestUIState.Events.Trigger("QuitGame");
   }
 }
 }

我們定義了一個菜單操作的抽象類用於繼承:

using UnityEngine;
 using System.Collections;
public class MenuUI {
 public virtual void DoGUI() {
}
 }

遊戲退出類代碼如下:

using UnityEngine;
 using System.Collections;
class QuitGameUI : MenuUI, IState {
 public void OnEnter(string prevState) {
   
 }

 public void OnExit(string nextState) {
   
 }

 public void OnUpdate() {
   
 }

 public override void DoGUI() {
   GUILayout.BeginHorizontal();
   if (GUILayout.Button("Confirm", GUILayout.Width(Screen.width / 2), GUILayout.Height(Screen.height))) {
    TestUIState.Events.Trigger("ConfirmQuit");
   }
   
   if (GUILayout.Button("Cancel", GUILayout.Width(Screen.width / 2), GUILayout.Height(Screen.height))) {
    TestUIState.Events.Trigger("CancelQuit");
   }
   GUILayout.EndHorizontal();
 }
 }

最後只要將下面的腳本掛到對象上就可以了:

using UnityEngine;
 using System.Collections;
public class TestUIState : MonoBehaviour {

 public static EventSystem.Dispatcher Events = new EventSystem.Dispatcher();

 public FiniteStateMachine FSM = new FiniteStateMachine();
public void Awake() {

   FSM.Register("MainMenu", new MainMenuUI());
   FSM.Register("AudioMenu", new AudioMenuUI());
   FSM.Register("MainGame", new MainGame(FSM));
   FSM.Register("QuitGame", new QuitGameUI());

   FSM.EntryPoint("MainMenu");

   FSM.State("MainMenu").On("OPEN_AUDIO").Enter("AudioMenu")
    .On("PLAY_GAME").Push("MainGame")
    .On("QUIT_GAME").Enter("QuitGame");

   FSM.State("QuitGame").On("PROCESS_QUIT", delegate(bool sure) {
     if (sure) {
      gameObject.GetComponent<TestUIState>().enabled = false;
      Camera.main.backgroundColor = Color.black;
     } else { FSM.Enter("MainMenu"); }
    });
   FSM.State("AudioMenu").On("BACK_TO_MENU").Enter("MainMenu");

   Events.On("OpenMainGame", delegate() { FSM.CurrentState.Trigger("PLAY_GAME"); });
   Events.On("OpenAudioMenu", delegate() { FSM.CurrentState.Trigger("OPEN_AUDIO"); });
   Events.On("QuitGame", delegate() { FSM.CurrentState.Trigger("QUIT_GAME"); });
  Events.On("ConfirmQuit", delegate() { FSM.CurrentState.Trigger("PROCESS_QUIT", true); });
   Events.On("CancelQuit", delegate() { FSM.CurrentState.Trigger("PROCESS_QUIT", false); });
  Events.On("BackToMenu", delegate() { FSM.CurrentState.Trigger("BACK_TO_MENU", false); });
 }
public void Update() {

   FSM.Update();
 }
void OnGUI() {
   if (FSM.CurrentState == null)
    return;
   MenuUI ui = (MenuUI)FSM.CurrentState.StateObject;
  ui.DoGUI();
 }
 }

測試用的代碼一共有AudioMenUI.cs    MainGame.cs  MainMenuUI.cs MenuUI.cs  QuitGameUI.cs  TestUIState.cs.大家只要將TestUIStat.cs掛到對象上就可以運行了。在使用的過程中可以先調試一下,前五章是給大家封裝的,可以直接將其運用到項目中。這裏的類只是跟大家分享一下如何去運用。


     有限狀態機到這裏就全部結束了。有什麼問題歡迎一起討論。接下來我會給大家分享其他技術點。。。。。



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章