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 state
     public virtual void Execute (entity_type entityType)
     {
         
     }
     //Exit state
     public 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有限狀態機來說,已經實現了其最主要的功能。不足之處歡迎大家提出討論,並幫助加以完善。

謝謝關注。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM