U3D-FSM有限狀態機的簡單設計

http://coder.beitown.com/archives/592

在之前的文章里介紹了一個基礎U3D狀態機框架(Unity3D游戲開發之狀態流框架)即大Switch的枚舉狀態控制。這種方法雖然容易理解,編程方法也相對簡單,但是弊端是當狀態變得復雜之后,或需要添加一種新的狀態時,會顯得非常混亂并且難以下手。故我們需要引進一種更高級的狀態機技術來避免這些問題。網上有一些講述U3D-FSM狀態機的文章,但都不針對基礎講解,而且大多帶有冗余的與狀態機不相關的代碼,基礎不好的讀者容易看不清FSM狀態機的核心所在。這里針對網上的一些文章和代碼做了一個整理,意圖使之簡單易懂。

這里關于FSM有限狀態機這類名詞的解釋這里就不再說明了,感興趣的朋友可以自己去百度下(度娘鏈接),本文只說重點。

首先是狀態機基類State.cs

/*** 狀態基類 */
public class State[entity_type>
{public entity_type Target;//Enter state  public virtual void Enter (entity_type entityType){}//Execute statepublic virtual void Execute (entity_type entityType){}//Exit statepublic virtual void Exit (entity_type entityType){}}

基類之所以設計成含有3個小的狀態方法是因為,通常在游戲中有些行為都只是在進入或退出某個狀態時出現的,并不會發生在通常的更新步驟中。這樣設計就可以有效的將持續性調用語句和一次性調用語句有效的區分開來。(舉例:發送技能時的特效,有些是持續性而有些又是一次性的)

接下來我們編寫狀態機代碼,來使直接的這個基類的各個方法運作起來:

using UnityEngine;
using System.Collections;public class StateMachine[entity_type>
{private entity_type m_pOwner;private State[entity_type> m_pCurrentState;//當前狀態private State[entity_type> m_pPreviousState;//上一個狀態private State[entity_type> m_pGlobalState;//全局狀態/*狀態機構造函數*/public StateMachine (entity_type owner){m_pOwner = owner;m_pCurrentState = null;m_pPreviousState = null;m_pGlobalState = null;}/*進入全局狀態*/public void GlobalStateEnter(){m_pGlobalState.Enter(m_pOwner);}/*設置全局狀態*/public void SetGlobalStateState(State[entity_type> GlobalState){m_pGlobalState = GlobalState;m_pGlobalState.Target = m_pOwner;m_pGlobalState.Enter(m_pOwner);}/*設置當前狀態*/public void SetCurrentState(State[entity_type> CurrentState){m_pCurrentState = CurrentState;m_pCurrentState.Target = m_pOwner;m_pCurrentState.Enter(m_pOwner);}/*Update*/public void SMUpdate (){if (m_pGlobalState != null)m_pGlobalState.Execute (m_pOwner);if (m_pCurrentState != null)m_pCurrentState.Execute (m_pOwner);}/*狀態改變*/public void ChangeState (State[entity_type> pNewState){if (pNewState == null) {Debug.LogError ("can't find this state");}//觸發退出狀態調用Exit方法
        m_pCurrentState.Exit(m_pOwner);//保存上一個狀態 m_pPreviousState = m_pCurrentState;//設置新狀態為當前狀態m_pCurrentState = pNewState;m_pCurrentState.Target = m_pOwner;//進入當前狀態調用Enter方法
        m_pCurrentState.Enter (m_pOwner);}public void RevertToPreviousState (){//切換到前一個狀態
        ChangeState (m_pPreviousState);}public State[entity_type> CurrentState (){//返回當前狀態return m_pCurrentState;}public State[entity_type> GlobalState (){//返回全局狀態return m_pGlobalState;}public State[entity_type> PreviousState (){//返回前一個狀態return m_pPreviousState;}}

這個狀態機其實還不是最簡的,全局和上一個狀態的相關部分都可以去掉,但同時功能上就會被削減,故這里將其保留。

現在狀態基類和狀態機類都有了,我們可以開始編寫游戲對象的獨立狀態類,先編寫游戲的總流程狀態類,這里命名為MainState.cs

/*** 全局狀態*/
public class MainState : State[Main>
{public static MainState instance;/*構造函數單例化*/public static MainState Instance(){if (instance == null)instance = new MainState();return instance;}public override void Enter(Main Entity){//這里添加進入此狀態時執行的代碼
    }public override void Execute(Main Entity){//這里添加持續此狀態刷新代碼
    }public override void Exit(Main Entity){//這里添加離開此狀態時執行代碼
    }}/*** Ready狀態*/
public class MainState_Ready : State[Main>
{public static MainState_Ready instance;/*構造函數單例化*/public static MainState_Ready Instance(){if (instance == null)instance = new MainState_Ready();return instance;}public override void Enter(Main Entity){//這里添加進入此狀態時執行的代碼
    }public override void Execute(Main Entity){//這里添加持續此狀態刷新代碼//這里是重點 當滿足某條件后 我們可以進行狀態切換 執行如下代碼 切換到 Run狀態
        Entity.GetFSM().ChangeState(MainState_Run.Instance());  }public override void Exit(Main Entity){//這里添加離開此狀態時執行代碼
    }
}/*** Run狀態*/
public class MainState_Run : State[Main>
{public static MainState_Run instance;/*構造函數單例化*/public static MainState_Run Instance(){if (instance == null)instance = new MainState_Run();return instance;}public override void Enter(Main Entity){//這里添加進入此狀態時執行的代碼
    }public override void Execute(Main Entity){//這里添加持續此狀態刷新代碼//當滿足某條件后 我們可以繼續進行狀態切換 執行如下代碼 切換到 Over狀態
        Entity.GetFSM().ChangeState(MainState_Over.Instance()); }public override void Exit(Main Entity){//這里添加離開此狀態時執行代碼
    }
}/*** Over狀態*/
public class MainState_Over : State[Main>
{public static MainState_Over instance;/*構造函數單例化*/public static MainState_Over Instance(){if (instance == null)instance = new MainState_Over();return instance;}public override void Enter(Main Entity){//這里添加進入此狀態時執行的代碼
    }public override void Execute(Main Entity){//這里添加持續此狀態刷新代碼//如之前兩個狀態類一樣 同理 當滿足一定狀態后 可以切換回Ready狀態
       Entity.GetFSM().ChangeState(MainState_Ready.Instance()); }public override void Exit(Main Entity){//這里添加離開此狀態時執行代碼
    }
}

代碼有點長,主要是為了讓大家能夠看清楚如何進行一個狀態的編寫,其實基類都是一樣的,都是重復內容。 這里我們看到,除了定義一個全局的狀態類之外,我們還添加了Ready、Run、Over三個狀態。重點注意一下Execute函數,這里是狀態切換的關鍵,當帶此狀態綁定的對象Update時就在不停的執行Execute里的代碼段,當滿足一定條件后,即達成狀態的切換。

這里我們看一下之前的狀態機代碼里的ChangeState方法,就知道整個狀態切換是如何工作的了:

/*狀態改變*/public void ChangeState (State[entity_type> pNewState){if (pNewState == null) {Debug.LogError ("can't find this state");}//觸發退出狀態調用Exit方法
        m_pCurrentState.Exit(m_pOwner);//保存上一個狀態 m_pPreviousState = m_pCurrentState;//設置新狀態為當前狀態m_pCurrentState = pNewState;m_pCurrentState.Target = m_pOwner;//進入當前狀態調用Enter方法
        m_pCurrentState.Enter (m_pOwner);}

可以看到當狀態切換時,會自動觸發當前狀態的Exit方法和目標狀態的Enter方法。這樣就完成了一整個狀態的切換過程。

到這里整個有限狀態機體系基本就算完工了,剩下的是如何在Main里進行MainState類的創建及使用,Main.cs代碼如下:

using UnityEngine;
using System.Collections;public class Main : MonoBehaviour{StateMachine[Main> m_pStateMachine;//定義一個狀態機void Start () {m_pStateMachine = new StateMachine[Main>(this);//初始化狀態機m_pStateMachine.SetCurrentState(MainState_Ready.Instance()); //設置一個當前狀態m_pStateMachine.SetGlobalStateState(MainState.Instance());//設置全局狀態
    }void Update (){   m_pStateMachine.SMUpdate();}/*返回狀態機*/public StateMachine[Main> GetFSM (){return m_pStateMachine;}}

寫到這里我們整個狀態機的框架及使用流程就基本結束了,這里要注意幾個問題: ①不要在SetCurrentState()方法調用前,調用ChangeState()方法,否則會出現null對象錯誤,具體原因很簡單,看一下ChangeState()里的代碼調用了哪些變量就知道了。 ②狀態間的通信,這個狀態機其實還是有未完善的地方的,目前狀態間的通知是通過直接調用其他狀態機的ChangeState()方法實現的,這樣勢必要先獲取該對象的腳本,這個功能待完善吧。 ③在U3D里每個游戲對象初始化并調用Start()方法的時機是不一樣的,所以要注意,開始游戲時不要直接進入開始狀態,而是要有一個等待態來讓所有的游戲對象完成Start()方法后再調用這些對象的狀態機。

另外,多個狀態機間的通信,就像上文②中所述那樣,僅僅是通過調用ChangeState()方法來實現,并不是非常完善,所以暫時不做講解,以免誤導大家,待日后有較好解決方案再另行開篇。 此FSM狀態機僅為一個雛形,還有很多功能及優化要做,但對于入門FSM有限狀態機來說,已經實現了其最主要的功能。不足之處歡迎大家提出討論,并幫助加以完善。

謝謝關注。

轉載于:https://www.cnblogs.com/softimagewht/p/4042431.html

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/273549.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/273549.shtml
英文地址,請注明出處:http://en.pswp.cn/news/273549.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

《Java 高并發》04 線程的基本操作

新建線程 新建線程很簡單。只要使用new 關鍵字創建一個線程對象,并且調用 start 方法啟動線程。 Thread t new Thread(); t.start();注意:run 方法不是用來啟動線程。如果調用 run 方法它只會作為普通方法來執行,而不會開啟線程執行。 終止…

Dispatch 方法簡介

后臺執行 dispatch_async(dispatch_get_global_queue(0, 0), ^{ //后臺程執行 something; }); 主線程執行 dispatch_async(dispatch_get_main_queue(), ^{// 主線程執行something; }); 一次性執行 static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ // 主…

linux雜七雜八整理

64系統里執行32位程序: 1、在64系統里執行32位程序如果出現/lib/ld-linux.so.2: bad ELF interpreter: No such file or directory,安裝下glic即可sudo yum install glibc.i6862、error while loading shared libraries: libz.so.1: cannot …

《Java 高并發》05 線程的基本操作

volatile 與 Java 內存模型 Java 內存模型都是圍繞著原子性、有序性和可見性展開的。為了在適當的場合,確保線程間的原子性、有序性和可見性。Java 使用了一些特許的操作或者關鍵字來申明、告訴虛擬機,在這個地方,要尤其注意,不能…

mybatis 2 -常用數據操作

1、寫入數據并獲取自增ID XML配置&#xff1a; <!-- 寫入數據獲取自增ID --><insert id"insertLog" parameterType"com.mamaguwen.entity.sys_loginlog" useGeneratedKeys"true" keyProperty"logid">insert into sys_…

Spring常用的的注解對應xml配置詳解

Component(value"")注解&#xff1a;組件 標記在類上&#xff0c;也可以放在接口上注解作用&#xff1a;把AccountDao實現類對象交由Spring IOC容器管理 相當于XML配置文件中的Bean標簽 <bean id"userAnnonMapper" class"com.spring.mapper.User…

安卓模擬器bluestacks mac地址修改教程

http://szmars2008.blog.163.com/blog/static/118893702201373181349348/ 轉載于:https://www.cnblogs.com/prayer521/p/4069037.html

Docker 搭建 ELK 日志系統,并通過 Kibana 查看日志

Docker 搭建 ELK 日志系統,并通過 Kibana 查看日志 docker-compose.yml version: 3 services:elasticsearch:image: elasticsearch:7.7.0 #鏡像container_name: elasticsearch #定義容器名稱restart: always #開機啟動&#xff0c;失敗也會一直重啟environment:- "cl…

蟠桃記

Problem Description 喜歡西游記的同學肯定都知道悟空偷吃蟠桃的故事&#xff0c;你們一定都覺得這猴子太鬧騰了&#xff0c;其實你們是有所不知&#xff1a;悟空是在研究一個數學問題&#xff01; 什么問題&#xff1f;他研究的問題是蟠桃一共有多少個&#xff01; 不過&#…

Spring 定時任務動態管理

管理 Spring 中定時任務 pom.xml <properties><hutool.version>5.6.6</hutool.version><lombok.version>1.18.20</lombok.version><spring-boot.web.version>2.2.10.RELEASE</spring-boot.web.version> </properties><de…

高效率Oracle SQL語句

1、Where子句中的連接順序&#xff1a; ORACLE采用自下而上的順序解析WHERE子句。 根據這個原理&#xff0c;表之間的連接必須寫在其他WHERE條件之前&#xff0c; 那些可以過濾掉最大數量記錄的條件必須寫在WHERE子句的末尾。 舉例&#xff1a; (低效) select ... from table1…

RabbitMQ Management:Management API returned status code 500

錯誤顯示&#xff1a; 解決方案&#xff1a; 因為是使用docker 容器安裝的&#xff0c;所有需要進入容器 docker exec -it rabbitmq /bin/bash進入目錄 cd /etc/rabbitmq/conf.d/執行命令 echo management_agent.disable_metrics_collector false > management_agent.dis…

Android JNI和NDK學習(5)--JNI分析API

Java類型和本地類型對應 在如下情況下&#xff0c;需要在本地方法中應用java對象的引用&#xff0c;就會用到類型之間的轉換&#xff1a; java方法里面將參數傳入本地方法&#xff1b;在本地方法里面創建java對象&#xff1b;在本地方法里面return結果給java程序。Java基本類型…

RabbitMq 消費失敗,重試機制

方案一&#xff1a; 本地消息表 定時任務 本地消息表&#xff1a;主要用于存儲 業務數據、交換機、隊列、路由、次數 定時任務&#xff1a;定時掃描本地消息表&#xff0c;重新給業務隊列投遞消息。 具體思路&#xff1a;業務隊列消費失敗時&#xff0c;把 業務數據、交換機、…

Android常用的工具類

主要介紹總結的Android開發中常用的工具類&#xff0c;大部分同樣適用于Java。目前包括HttpUtils、DownloadManagerPro、ShellUtils、PackageUtils、 PreferencesUtils、JSONUtils、FileUtils、ResourceUtils、StringUtils、 ParcelUtils、RandomUtils、ArrayUtils、ImageUtils…

0. Spring 基礎

BeanDefinition BeanDefinition 表示 Bean 定義&#xff1a; Spring根據BeanDefinition來創建Bean對象&#xff1b;BeanDefinition有很多的屬性用來描述Bean&#xff1b;BeanDefiniton是Spring中非常核心的概念。BeanDefiniton中重要的屬性&#xff1a; a. beanClass&#xf…

1. Spring 源碼:Spring 解析XML 配置文件,獲得 Bena 的定義信息

通過 Debug 運行 XmlBeanDefinitionReaderTests 類的 withFreshInputStream() 的方法&#xff0c;調試 Spring 解析 XML 配置文件&#xff0c;獲得 Bean 的定義。 大體流程可根據序號查看&#xff0c;xml 配置文件隨便看一眼&#xff0c;不用過多在意。 <?xml version&qu…

c++ 讀取文件 最后一行讀取了兩次

用ifstream的eof()&#xff0c;竟然讀到文件最后了&#xff0c;判斷eof還為false。網上查找資料后&#xff0c;終于解決這個問題。 參照文件&#xff1a;http://tuhao.blogbus.com/logs/21306687.html 在使用C/C讀文件的時候&#xff0c;一定都使用過eof&#xff08;&#xff0…

java中的io系統詳解(轉)

Java 流在處理上分為字符流和字節流。字符流處理的單元為 2 個字節的 Unicode 字符&#xff0c;分別操作字符、字符數組或字符串&#xff0c;而字節流處理單元為 1 個字節&#xff0c;操作字節和字節數組。 Java 內用 Unicode 編碼存儲字符&#xff0c;字符流處理類負責將外部的…

js獲取字符串最后一個字符代碼

方法一&#xff1a;運用String對象下的charAt方法 charAt() 方法可返回指定位置的字符。 代碼如下 復制代碼 str.charAt(str.length – 1) 請注意&#xff0c;JavaScript 并沒有一種有別于字符串類型的字符數據類型&#xff0c;所以返回的字符是長度為 1 的字符串 方法二&#…