Unity C# | 狀態模式(State Diagram)

更新於 發佈於 閱讀時間約 12 分鐘

一、前言

  這篇文章將會講述設計模式中的狀態模式,其資料源自於書籍、網路、個人理解,從簡介→架構→撰寫→測試的整個流程進行介紹與分享。

1. 書籍《設計模式與遊戲開發的完美結合》

  我從班導師的研究室中找到一本書,書籍的名稱叫做《設計模式與遊戲開發的完美結合(Design Pattrns in Game Development)》,我買來這本書籍有一段時間了,這一個暑假正式開始研究它。

2. 網路資源

  在網路上我找到了很多人的教學,有些人比較直接,拍著書籍畫面就開始介紹這個設計方法,其內容看起來與其說是文章,不如說是心得;也有一些講述很完整,都很值得閱讀與參考。

3. 個人理解

  這篇文章將會結合我的個人理解,包含我是怎麼理解狀態模式,有沒有比較形象且具體的理解方式,我期許這篇文章將會幫助「想要學習狀態模式」或「想要複習狀態模式」的讀者。

  有時候我會回來看看我自己寫的文章,這極大的幫助我複習我所學習的知識與技術,這個暑假如果有時間與精力,我會考慮把文章做一個系統性的整理,發佈在 Medium 或 Blogger 上面。

二、簡介-狀態模式(State Diagram)

  替物件導向構思了設計模式(Disign Pattern)的四位作者,被暱稱為四人幫(Gof),其初衷為:「每一種設計模式都在說明一個一再出現的問題,並描述解決方案的核心,讓設計師能夠據以變化,產生出各種招式來,解決上萬個類似的問題。」

1. 定義

  在狀態模式(State Diagram)中的定義為:「讓一個物件的行為隨著狀態的改變而變化,而該物件也像是換了類別一樣。」

  在這句話中,有一個容易被忽略的重點:這個物件沒有改變,玩家依然看到同樣的物件,具有相同的架構與組成;玩家要控制的函式依然是同一個,只是裡面的執行內容不同了。

  像是魔法師會施放法術,玩家在操控這位魔法師的時候,它只需要按下鍵盤上的「Q鍵」施放「法術」,而不需要知道實際上是「小火球」還是「閃電」的效果,也不用思考有幾種法術要切換。

2. 用途

  狀態模式是常被使用的設計模式,它可以被拿來運用於關卡場景,讓場景切換不那麼死板,甚至附加一些規則;也能拿來被使用於角色狀態,給予角色不同的狀態欄位,只需要添加上程式腳本就可以完成擴充與切換;運用在敵人AI上面,讓敵人具有更多變的動作與維護性。

3. 重要性

  狀態模式的重要性很高,它具有高內聚性、鬆耦合性的特色,這點我在一篇講述耦合內聚的文章有說過,這是一種利於維護、可讀性高,無論擴充還是刪減都很方便的一種優秀設計。

  因此其擴展性與適應性都很不錯,能夠讓遊戲角色、敵人、關卡等等,與玩家的互動更具彈性和多樣性,這讓遊戲開發人員能輕鬆新增更多的狀態和相應的行為,卻不會影響現有的程式碼,這種可擴展性使得遊戲的功能和內容可以逐步擴充和升級。

三、架構-狀態模式(State Diagram)

raw-image

  在這一次的介紹中,狀態模式總共有五個程式腳本,依據我個人的理解,可以粗淺的分類為管理者與封裝內容,我們可以通過 Request() 函式來跟管理者要求執行狀態,並用 SetState() 來跟管理者要求替換狀態。

  當狀態模式(State Diagram)全部撰寫完成以後,管理者是其他程式腳本唯一可以調用的內容,其中狀態(State)與具體狀態(Concrete State)都是不可以調用的封裝內容。

  如果未來要更新具體狀態,也就是讓玩家多出一個新的狀態、或讓敵人新增一個新的判斷邏輯,程式設計師只需要新建一個程式腳本,就可以多出一個新的具體狀態(ex. ConcreteStateD),而不需要修改任何一個程式腳本。

1. 環境(Context)

  這個英文單字的中文翻譯是上下文或語境,在英漢字典中,我認為有一個更貼切的翻譯是環境圖(Context Diagram),環境中會有單個或複數個狀態,而詳細的狀態則可以多達上萬個。

  環境程式腳本是一個普通的類別,裡面帶有私有的狀態欄位,可以設定新的狀態給它,或要求它執行該狀態的行為。

2. 狀態(State)

  這是一個抽象類別,具有一個建構式與一個抽象函式,建構式是一個帶參數的建構式,初始化的內容為指定具體的環境(Context),抽象函式則是規定所有具體狀態,它們都必須持有一個要執行的函式。

  我不完全理解抽象(Abstract)與介面(Interface)的差異,抽象只能被一個程式腳本繼承,並且可以寫一些具體的執行內容;介面可以被多個程式腳本繼承,不過無法寫具體的執行內容。

  使用抽象(Abstract)而非介面(Interface)的原因,我推測是因為單一繼承的特性,或存在非公用(Public)的建構式,所以才使用抽象,否則我猜使用抽象或介面應該都可以完成狀態模式(State Diagram)的撰寫。

3. 具體狀態(Concrete State)

  這是一個繼承自狀態(State)的類別,可以有複數類似的程式腳本,它具有一個建構式、實例化的抽象函式,建構式除了自己本身以外,還會呼叫基底類別的建構函式,初始化的功能是指定一個具體的環境。

  實例化的抽象函式則是實作基底類別的抽象函式,每個類似的程式腳本可以依據自己的需求撰寫不同的功能。

四、撰寫-狀態模式(State Diagram)

  接下來我們談談程式撰寫的具體範例,所有的程式腳本都不需要使用Unity內建的 MonoBehaviour,如果沒有刪除,在測試時使用建構函式的過程會讓系統警告你實例化了 Monobehaviour。

  我們會從環境(Context)開始撰寫,接下來定義抽象類別(State),最後撰寫三個具體狀態(Concrete State)。

1. 環境(Context)

public class Context
{
  State m_State = null;
  
  public void Request(int Value)
  {
    m_state.Handle(value);
  }
  
  public void SetState(State theState)
  {
    Debug.Log("Context.SetState:" + theState);
    m_State = theState;
  }
}

  為了方便測試,環境(Context)要求具體狀態執行的內容,我們採用簡單的數值計算,並且在設定一個新的狀態中,添加一個除錯用的說明訊息。

2. 狀態(State)

public abstract class State
{
  protected Context m_Context = null;
  public State (Context theContext)
  {
    m_Context = theContext;
  }
  public abstract void Handle (int Value);
}

  其中函式 State 沒有 void 卻不用使用回傳值(return),是因為它跟類別名稱相同,因此會被判斷為一個建構式,有興趣了解建構式可以參考下列文章。

《Unity C# | 建構式(constructor)》

3. 具體狀態(Concrete State)

public class ConcreteStateA : State
{
  public ConcreteStateA(Context theContext) : Base (the Context)
  {}
  
  public overrid void Handle (int Value)
  {
    Debug.Log("ConcreteStateA.Handle");
    if ( Value > 10)
      m_Context.SetState ( new ConcreteStateB(m_Context );
  }
}

public class ConcreteStateB : State
{
  public ConcreteStateB(Context theContext) : Base (the Context)
  {}
  
  public overrid void Handle (int Value)
  {
    Debug.Log("ConcreteStateB.Handle");
    if ( Value > 20)
      m_Context.SetState ( new ConcreteStateC(m_Context );
  }
}

public class ConcreteStateC : State
{
  public ConcreteStateC(Context theContext) : Base (the Context)
  {}
  
  public overrid void Handle (int Value)
  {
    Debug.Log("ConcreteStateC.Handle");
    if ( Value > 30)
      m_Context.SetState ( new ConcreteStateA(m_Context );
  }
}

  為了方便測試,數值每到一個階段 ,就會切換狀態。

五、測試-狀態模式(State Diagram)

  接下來,我們可以創建一個新的 Unity 程式腳本:

public class UnitTest : MonoBehaviour
{
private void Start()
    UnitTest_implement();

private void UnitTest_implement()
{
Context context = new Context();
context.SetState(new ConcreteStateA(context));

context.Request(5);
context.Request(15);
context.Request(25);
context.Request(35);
}
}

1. 建立新的物件 / 物件實例化

  在「Context context = new Context();」的過程中,我們創建了一個新的環境(Context)欄位,並且使用建構式創建一個新的環境給指派進去。

2. 指定具體狀態

  隨後,我們指定了一個新的具體狀態給這個新環境,也用類似的做法創建一個新的具體狀態A,並且把這個新環境給具體狀態A。

3. 測試不同內容

  隨後就是測試整個狀態模式可能會有的所有狀態了,詳細的就不多說,可以直接看結果,核對是否正確。

raw-image

六、後記

  當初在學習狀態模式的時候,我卡在狀態類別(State)中的建構式,我一直沒有看懂這個函式是幹嘛的,那個時候我還沒有注意到它跟類別名稱一樣,直到我清楚建構式的概念後,我才算是真正學會了狀態模式。

  閱讀這本書籍以後,我才理解所謂的單元測試是在幹嘛,測試程式中的所有階段,以及可能遇到的狀態,而我以前寫的所有程式,根本都不到需要使用單元測試的階段,當然不需要。



留言
avatar-img
留言分享你的想法!
avatar-img
瓶裝雪的沙龍
108會員
247內容數
對設計師如何成長為設計師好奇嗎? 2020年九月,我進入大學學習當一位設計師,從開始到沉寂,再到重燃熱忱,我將在方格子紀錄我的成長歷程、理念、心情,分享我在這段旅程中所經歷的故事。
瓶裝雪的沙龍的其他內容
2024/09/22
魔火術師是第三人稱動作射擊遊戲,屬於專題畢業製作,在畢業組成新的團隊後優化並上架。
Thumbnail
2024/09/22
魔火術師是第三人稱動作射擊遊戲,屬於專題畢業製作,在畢業組成新的團隊後優化並上架。
Thumbnail
2024/01/26
這篇文章將會講述虛擬(virtual)與覆蓋(override)的簡易使用方式。
Thumbnail
2024/01/26
這篇文章將會講述虛擬(virtual)與覆蓋(override)的簡易使用方式。
Thumbnail
2023/12/29
講述遊戲設計從新手到專業的發展方向。
Thumbnail
2023/12/29
講述遊戲設計從新手到專業的發展方向。
Thumbnail
看更多
你可能也想看
Thumbnail
沙龍一直是創作與交流的重要空間,這次 vocus 全面改版了沙龍介面,就是為了讓好內容被好好看見! 你可以自由編排你的沙龍首頁版位,新版手機介面也讓每位訪客都能更快找到感興趣的內容、成為你的支持者。 改版完成後可以在社群媒體分享新版面,並標記 @vocus.official⁠ ♥️ ⁠
Thumbnail
沙龍一直是創作與交流的重要空間,這次 vocus 全面改版了沙龍介面,就是為了讓好內容被好好看見! 你可以自由編排你的沙龍首頁版位,新版手機介面也讓每位訪客都能更快找到感興趣的內容、成為你的支持者。 改版完成後可以在社群媒體分享新版面,並標記 @vocus.official⁠ ♥️ ⁠
Thumbnail
每年4月、5月都是最多稅要繳的月份,當然大部份的人都是有機會繳到「綜合所得稅」,只是相當相當多人還不知道,原來繳給政府的稅!可以透過一些有活動的銀行信用卡或電子支付來繳,從繳費中賺一點點小確幸!就是賺個1%~2%大家也是很開心的,因為你們把沒回饋變成有回饋,就是用卡的最高境界 所得稅線上申報
Thumbnail
每年4月、5月都是最多稅要繳的月份,當然大部份的人都是有機會繳到「綜合所得稅」,只是相當相當多人還不知道,原來繳給政府的稅!可以透過一些有活動的銀行信用卡或電子支付來繳,從繳費中賺一點點小確幸!就是賺個1%~2%大家也是很開心的,因為你們把沒回饋變成有回饋,就是用卡的最高境界 所得稅線上申報
Thumbnail
全球科技產業的焦點,AKA 全村的希望 NVIDIA,於五月底正式發布了他們在今年 2025 第一季的財報 (輝達內部財務年度為 2026 Q1,實際日曆期間為今年二到四月),交出了打敗了市場預期的成績單。然而,在銷售持續高速成長的同時,川普政府加大對於中國的晶片管制......
Thumbnail
全球科技產業的焦點,AKA 全村的希望 NVIDIA,於五月底正式發布了他們在今年 2025 第一季的財報 (輝達內部財務年度為 2026 Q1,實際日曆期間為今年二到四月),交出了打敗了市場預期的成績單。然而,在銷售持續高速成長的同時,川普政府加大對於中國的晶片管制......
Thumbnail
重點摘要: 6 月繼續維持基準利率不變,強調維持高利率主因為關稅 點陣圖表現略為鷹派,收斂 2026、2027 年降息預期 SEP 連續 2 季下修 GDP、上修通膨預測值 --- 1.繼續維持利率不變,強調需要維持高利率是因為關稅: 聯準會 (Fed) 召開 6 月利率會議
Thumbnail
重點摘要: 6 月繼續維持基準利率不變,強調維持高利率主因為關稅 點陣圖表現略為鷹派,收斂 2026、2027 年降息預期 SEP 連續 2 季下修 GDP、上修通膨預測值 --- 1.繼續維持利率不變,強調需要維持高利率是因為關稅: 聯準會 (Fed) 召開 6 月利率會議
Thumbnail
這篇文章將會講述設計模式中的狀態模式,其資料源自於書籍、網路、個人理解,從簡介→架構→撰寫→測試的整個流程進行介紹與分享。
Thumbnail
這篇文章將會講述設計模式中的狀態模式,其資料源自於書籍、網路、個人理解,從簡介→架構→撰寫→測試的整個流程進行介紹與分享。
Thumbnail
這篇文章將會介紹建構式(Constructor),這是一個隱藏在程式腳本中的重要知識,我們極常使用建構式,但新手程式員幾乎不會知道它。
Thumbnail
這篇文章將會介紹建構式(Constructor),這是一個隱藏在程式腳本中的重要知識,我們極常使用建構式,但新手程式員幾乎不會知道它。
Thumbnail
一、前言   這篇文章將會簡單介紹物件導向的設計模式,以及學習這項技術需要有哪些先備知識,學習才會比較順利。 二、設計模式   在物件導向中,除了最基本的五大原則以外,其實還有一知名的概念,那就是設計模式(Design Pattern),它能運用到很多層面。 1. 解決一再出現的問題   許多程式設
Thumbnail
一、前言   這篇文章將會簡單介紹物件導向的設計模式,以及學習這項技術需要有哪些先備知識,學習才會比較順利。 二、設計模式   在物件導向中,除了最基本的五大原則以外,其實還有一知名的概念,那就是設計模式(Design Pattern),它能運用到很多層面。 1. 解決一再出現的問題   許多程式設
Thumbnail
這篇文章將會講述這款遊戲的開發預計與遊戲內系統的清單粗估。
Thumbnail
這篇文章將會講述這款遊戲的開發預計與遊戲內系統的清單粗估。
Thumbnail
這篇文章將會講述類圖的基本介紹,並且詳細敘述從零開始製作完整的類圖流程。
Thumbnail
這篇文章將會講述類圖的基本介紹,並且詳細敘述從零開始製作完整的類圖流程。
Thumbnail
這篇文章將會講述企劃撰寫給程式員的邏輯流程圖。
Thumbnail
這篇文章將會講述企劃撰寫給程式員的邏輯流程圖。
Thumbnail
小心設計模式別亂用 📷 介紹 設計模式就是過去的人,根據常見的軟體設計的問題,提出的解決方案。 設計模式總共有23種,根據情境分成三大類型,建立型、結構型、行為型。 建立型模式(Creational Patterns) 簡單工廠(Simple Factory) 工廠方法(Factory) 抽象工廠
Thumbnail
小心設計模式別亂用 📷 介紹 設計模式就是過去的人,根據常見的軟體設計的問題,提出的解決方案。 設計模式總共有23種,根據情境分成三大類型,建立型、結構型、行為型。 建立型模式(Creational Patterns) 簡單工廠(Simple Factory) 工廠方法(Factory) 抽象工廠
追蹤感興趣的內容從 Google News 追蹤更多 vocus 的最新精選內容追蹤 Google News