Unity3D架构系列之- FSM有限状态机设计六(总结)

原文:http://www.manew.com/thread-37964-1-1.html


         由于最近一直赶项目进度,没时间写,昨晚终于项目终于完成了,空闲下来,做一个总结。在这一篇中主要是把前五章一起总结一下,以及通过举例演示如何使用?有限状态机在游戏中使用的地方非常多,比如我们界面之间的切换,角色的状态切换等等。所以非常值得大家去学习一下,接下来我们主要实现的功能,为了表达清楚,我通过图例给大家说明一下:


给大家解析一下,程序运行首先进入主菜单,里面有三个按钮,开始游戏,音量,退出游戏。先从第一个说起,如果是开始游戏,它会进入到下一个界面游戏界面,游戏界面有个返回主菜单功能。二者可以互相切换。接下来是音量按钮,整个按钮是调节音量的,调节好了后,点确认和取消都是返回主菜单。二者之间互相切换,最后一个是退出游戏,会进入是否退出界面,如果否,返回主界面,如果是真正的关闭游戏。我们就把这个简单的功能用我们的有限状态机实现一下:

首先我们声明两个对象:

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挂到对象上就可以运行了。在使用的过程中可以先调试一下,前五章是给大家封装的,可以直接将其运用到项目中。这里的类只是跟大家分享一下如何去运用。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值