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

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

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

首先我们声明两个对象:

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

有限状态机到这里就全部结束了。有什么问题欢迎一起讨论。接下来我会给大家分享其他技术点。。。。。

时间: 2024-12-26 22:51:26

U3D架构系列之- FSM有限状态机设计六(总结篇)的相关文章

U3d架构系列之-FSM有限状态机设计一

我们在游戏开发中经常面临架构设计问题,在蛮牛问答里面也有好多朋友问关于架构方面的问题,在这里我就将一些经常使用的游戏开发方面的知识跟大家介绍 一下, 一是给大家提供一个设计思路,在此基础上可以举一反三,二是大家可以通过这个平台共同学习.闲话不多说了,切入正题,FSM有限状态机,在游戏开发中如何使用?那些模块的设计需要它?接下来我们就分析一下游戏: 比如我们的玩家自身的状态切换,Idle,walk,attack等等,这些都属于状态的切换,可以用有限状态机:还有比如玩家去完任务:领取任务,杀怪,交易

U3D架构系列之- FSM有限状态机设计四

接下来,我们继续我们的FSM有限状态机的设计,在设计三中我们实现了FSState这个类,我们继续实现FSEvent事件处理类.在FSEvent类里面我们声明了FiniteStateMachine里面定义的委托函数.代码如下: protected FiniteStateMachine.EnterState mEnterDelegate; protected FiniteStateMachine.PushState mPushDelegate; protected FiniteStateMachin

U3D架构系列之- FSM有限状态机设计五

在设计五中,我们把事件系统EventSystem实现一下.这个EventSystem类主要实现的是事件的注册,触发,以及分发Event消息的作用.提供接口对外使用的.在这里面为了功能扩展方便用了好多模板函数,方便大家调用. 分发消息的时候,我们采用的是队列的方式,就是先进先出原则. 首先要把消息事件注册一下,就是将其放到我们预先定义的表里面.函数代码如下所示: int Register(string eventName, Func<object,object,object,bool> acti

U3D架构系列之- FSM有限状态机设计三

在设计二中,我们实现了有限状态机管理类,接下来,我们实现FSState这个类,这里类主要是状态的基本操作以及事件触发.在这里我们定义了在FiniteStateMachine类里声明的三个委托.在FSState里面使用的代码如下: protected FiniteStateMachine.EnterState mEnterDelegate; protected FiniteStateMachine.PushState mPushDelegate; protected FiniteStateMach

C#进阶系列——DDD领域驱动设计初探(六):领域服务

前言:之前一直在搭建项目架构的代码,有点偏离我们的主题(DDD)了,这篇我们继续来聊聊DDD里面另一个比较重要的知识点:领域服务.关于领域服务的使用,书中也介绍得比较晦涩,在此就根据博主自己的理解谈谈这个知识点的使用. DDD领域驱动设计初探系列文章: C#进阶系列——DDD领域驱动设计初探(一):聚合 C#进阶系列——DDD领域驱动设计初探(二):仓储Repository(上) C#进阶系列——DDD领域驱动设计初探(三):仓储Repository(下) C#进阶系列——DDD领域驱动设计初探

大型网站架构系列:消息队列(二)

本文是大型网站架构系列:消息队列(二),主要分享JMS消息服务,常用消息中间件(Active MQ,Rabbit MQ,Zero MQ,Kafka).[第二篇的内容大部分为网络资源的整理和汇总,供大家学习总结使用,最后有文章来源] 本次分享大纲 消息队列概述(见第一篇:大型网站架构系列:分布式消息队列(一)) 消息队列应用场景(见第一篇:大型网站架构系列:分布式消息队列(一)) 消息中间件示例(见第一篇:大型网站架构系列:分布式消息队列(一)) JMS消息服务 常用消息队列 参考(推荐)资料 本

大型网站架构系列:20本技术书籍推荐

学习是技术人员成长的基础,本次分享20本技术方面的书籍,这些书不是每一本都是经典,但是每一本都有其特点.以下20本大部分本人都看过,因此推荐给大家.(本次推荐的20本只是一个参考,比如像Head First,Java编程思想等经典书籍是大家都知道,因此不在推荐之列) 本次分享大纲 大型网站架构系列 分布式系统系列 BAT技术文学系列 架构设计系列 本次分享总结 一.大型网站架构系列 第一本:<大型网站技术架构:核心原理与案例分析> 这是本算是国内大型网站架构的经典之作,由阿里人李智慧创作,听名

大型网站架构系列:消息队列(二) (转)

本文是大型网站架构系列:消息队列(二),主要分享JMS消息服务,常用消息中间件(Active MQ,Rabbit MQ,Zero MQ,Kafka).[第二篇的内容大部分为网络资源的整理和汇总,供大家学习总结使用,最后有文章来源] 本次分享大纲 消息队列概述(见第一篇:大型网站架构系列:分布式消息队列(一)) 消息队列应用场景(见第一篇:大型网站架构系列:分布式消息队列(一)) 消息中间件示例(见第一篇:大型网站架构系列:分布式消息队列(一)) JMS消息服务 常用消息队列 参考(推荐)资料 本

【转载】大型网站架构系列20本书

[转载]http://www.cnblogs.com/chy2055/p/5181352.html 学习是技术人员成长的基础,本次分享20本技术方面的书籍,这些书不是每一本都是经典,但是每一本都有其特点.以下20本大部分本人都看过,因此推荐给大家.(本次推荐的20本只是一个参考,比如像Head First,Java编程思想等经典书籍是大家都知道,因此不在推荐之列) 本次分享大纲 大型网站架构系列 分布式系统系列 BAT技术文学系列 架构设计系列 本次分享总结 一.大型网站架构系列 第一本:<大型