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

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

一、前言

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

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

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

2. 網路資源

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

3. 個人理解

  這篇文章將會結合我的個人理解,包含我是怎麼理解狀態模式,有沒有比較形象且具體的理解方式,我期許這篇文章將會幫助「想要學習狀態模式」或「想要複習狀態模式」的讀者。
  有時候我會回來看看我自己寫的文章,這極大的幫助我複習我所學習的知識與技術,這個暑假如果有時間與精力,我會考慮把文章做一個系統性的整理,發佈在 Medium 或 Blogger 上面。

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

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

1. 定義

  在狀態模式(State Diagram)中的定義為:「讓一個物件的行為隨著狀態的改變而變化,而該物件也像是換了類別一樣。」
  在這句話中,有一個容易被忽略的重點:這個物件沒有改變,玩家依然看到同樣的物件,具有相同的架構與組成;玩家要控制的函式依然是同一個,只是裡面的執行內容不同了。
  像是魔法師會施放法術,玩家在操控這位魔法師的時候,它只需要按下鍵盤上的「Q鍵」施放「法術」,而不需要知道實際上是「小火球」還是「閃電」的效果,也不用思考有幾種法術要切換。

2. 用途

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

3. 重要性

  狀態模式的重要性很高,它具有高內聚性、鬆耦合性的特色,這點我在一篇講述耦合內聚的文章有說過,這是一種利於維護、可讀性高,無論擴充還是刪減都很方便的一種優秀設計。
  因此其擴展性與適應性都很不錯,能夠讓遊戲角色、敵人、關卡等等,與玩家的互動更具彈性和多樣性,這讓遊戲開發人員能輕鬆新增更多的狀態和相應的行為,卻不會影響現有的程式碼,這種可擴展性使得遊戲的功能和內容可以逐步擴充和升級。

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

  在這一次的介紹中,狀態模式總共有五個程式腳本,依據我個人的理解,可以粗淺的分類為管理者與封裝內容,我們可以通過 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),是因為它跟類別名稱相同,因此會被判斷為一個建構式,有興趣了解建構式可以參考下列文章。

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. 測試不同內容

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

六、後記

  當初在學習狀態模式的時候,我卡在狀態類別(State)中的建構式,我一直沒有看懂這個函式是幹嘛的,那個時候我還沒有注意到它跟類別名稱一樣,直到我清楚建構式的概念後,我才算是真正學會了狀態模式。
  閱讀這本書籍以後,我才理解所謂的單元測試是在幹嘛,測試程式中的所有階段,以及可能遇到的狀態,而我以前寫的所有程式,根本都不到需要使用單元測試的階段,當然不需要。
為什麼會看到廣告
avatar-img
106會員
247內容數
對設計師如何成長為設計師好奇嗎? 2020年九月,我進入大學學習當一位設計師,從開始到沉寂,再到重燃熱忱,我將在方格子紀錄我的成長歷程、理念、心情,分享我在這段旅程中所經歷的故事。
留言0
查看全部
avatar-img
發表第一個留言支持創作者!
瓶裝雪的沙龍 的其他內容
這篇文章將會介紹建構式(Constructor),這是一個隱藏在程式腳本中的重要知識,我們極常使用建構式,但新手程式員幾乎不會知道它。
這篇文章將會講述 Unity Visual Effect Graph 的系統簡介,並且簡單介紹這項系統主線上的四個核心組件。
這篇文章將會講述子彈射擊與命中的思路,不包含實際程式設計。
一、前言   這篇文章將會簡單介紹物件導向的設計模式,以及學習這項技術需要有哪些先備知識,學習才會比較順利。 二、設計模式   在物件導向中,除了最基本的五大原則以外,其實還有一知名的概念,那就是設計模式(Design Pattern),它能運用到很多層面。 1. 解決一再出現的問題   許多程式設
這篇文章將會講述最近的一個程式設計體悟,並且分享近期要參加的獨立遊戲製作者聚會,會展示課程的其中一個作品。
這篇文章將會分享與老師討論,在遊戲設計中設計元素之間的比重與遊戲要注重的設計重點或賣點。
這篇文章將會介紹建構式(Constructor),這是一個隱藏在程式腳本中的重要知識,我們極常使用建構式,但新手程式員幾乎不會知道它。
這篇文章將會講述 Unity Visual Effect Graph 的系統簡介,並且簡單介紹這項系統主線上的四個核心組件。
這篇文章將會講述子彈射擊與命中的思路,不包含實際程式設計。
一、前言   這篇文章將會簡單介紹物件導向的設計模式,以及學習這項技術需要有哪些先備知識,學習才會比較順利。 二、設計模式   在物件導向中,除了最基本的五大原則以外,其實還有一知名的概念,那就是設計模式(Design Pattern),它能運用到很多層面。 1. 解決一再出現的問題   許多程式設
這篇文章將會講述最近的一個程式設計體悟,並且分享近期要參加的獨立遊戲製作者聚會,會展示課程的其中一個作品。
這篇文章將會分享與老師討論,在遊戲設計中設計元素之間的比重與遊戲要注重的設計重點或賣點。
你可能也想看
Google News 追蹤
Thumbnail
大家好,我是woody,是一名料理創作者,非常努力地在嘗試將複雜的料理簡單化,讓大家也可以體驗到料理的樂趣而我也非常享受料理的過程,今天想跟大家聊聊,除了料理本身,料理創作背後的成本。
Thumbnail
哈囉~很久沒跟各位自我介紹一下了~ 大家好~我是爺恩 我是一名圖文插畫家,有追蹤我一段時間的應該有發現爺恩這個品牌經營了好像.....快五年了(汗)時間過得真快!隨著時間過去,創作這件事好像變得更忙碌了,也很開心跟很多厲害的創作者以及廠商互相合作幫忙,還有最重要的是大家的支持與陪伴🥹。  
Thumbnail
嘿,大家新年快樂~ 新年大家都在做什麼呢? 跨年夜的我趕工製作某個外包設計案,在工作告一段落時趕上倒數。 然後和兩個小孩過了一個忙亂的元旦。在深夜時刻,看到朋友傳來的解籤網站,興致勃勃熬夜體驗了一下,覺得非常好玩,或許有人玩過了,但還是想寫上來分享紀錄一下~
Thumbnail
模擬世界是我們寫程式造出來的,我們就是模擬世界的主宰,所以各種作用力要長什麼樣子、要怎麼個作用法,都由我們決定。不過,如果希望這些作用力看起來像真實世界的作用力一樣,那在寫程式的時候,套用這些作用力在真實世界中的物理公式,會是比較省時省力的做法。
Thumbnail
這篇內容,將會講解什麼是腳本函式,以及與腳本函式相關的知識。包括腳本的簡介、使用函式(或全域變數)的注意事項、定義全域變數、定義函式、什麼是宣告、局部變數的應用。
Thumbnail
這篇內容,將會講解什麼是變數範圍,以及與變數範圍相關的知識。包括變數範圍的簡介、實體變數、全域變數、局部變數、常數。
Thumbnail
本文介紹了在網站開發中如何運用狀態機的原則和設計方法。通過具體案例分析,以及狀態和數據的區分,詳細介紹了狀態機的設計原則和應用。讀者可以通過本文瞭解如何將狀態機應用於實際的網站開發中。
※ 工廠模式 定義: 工廠模式是一種實現了「工廠」概念的物件導向設計模式。它提供一個通用的工廠介面,將創建instance(實例)的程式碼交由子類別各自實現,並根據需求去動態地生成相應的物件。這種模式將物件的創建邏輯與使用邏輯分開,使程式碼更容易維護和擴展。 特點: 具有高度標準化和同質性的
※ 單例模式介紹 ※ 定義:單例模式是一種設計模式,確保一個class(類)只有一個實例,並提供一個存取它的全域存取點。無論如何取值,皆只對這個實例取值。 ※ 目的:保證一個類別只會產生一個物件,而且提供存取該物件的統一方法。 ※ 講解:單例模式確保一個類無論怎麼 new 或 get,都只能拿
Thumbnail
動態規劃Dynamic Programming其實是 一種泛用的演算法思考方式與演算法建構框架。 動態規劃並不拘束於只能解課本上特定的的範例題。 只要我們能找出DP狀態定義、DP遞迴結構、初始條件(終止條件),就能適用動態規劃來解題,以數學的形式表達,並且在紙筆上或者電腦上、計算機上計算
Thumbnail
完成了Debug.log()的測試,接著還是要跟各位簡單講一下C#的一些規則,之後看程式會(比較)看得懂。 又講到變數? 在Unity中,變數是重要的工具,用來儲存和管理資料。讓開發者能夠靈活調整遊戲的行為和性能,減少代碼的重複性,使得遊戲開發更加高效和簡潔。透過使用變數,開發者可以輕鬆修改資料
Thumbnail
Heuristic 啟發式設計是用戶體驗設計的重要原則之一,涉及了許多與真實世界匹配、用戶控制、一致性和準則等方面,通過一些具體的例子解釋了這些原則的重要性。本文還提供了一些相關的視覺系統狀態和系統與真實世界匹配的例子,同時附帶了一些相關的教程和資料來源。
物件導向設計的一個重點就是封裝,這有很多層面上的意義,但基本上就是控制物件的成員變數和方法的存取權。物件導向的封裝還跟繼承機制有關,這使得有一些時候我們逼不得已必須把函式定義在類別上,這種做法使得物件的功能變得難以拆解。封裝應該是模組的職責,並不需要再給物件相同的能力。 一般的模組系統就是把相
Thumbnail
大家好,我是woody,是一名料理創作者,非常努力地在嘗試將複雜的料理簡單化,讓大家也可以體驗到料理的樂趣而我也非常享受料理的過程,今天想跟大家聊聊,除了料理本身,料理創作背後的成本。
Thumbnail
哈囉~很久沒跟各位自我介紹一下了~ 大家好~我是爺恩 我是一名圖文插畫家,有追蹤我一段時間的應該有發現爺恩這個品牌經營了好像.....快五年了(汗)時間過得真快!隨著時間過去,創作這件事好像變得更忙碌了,也很開心跟很多厲害的創作者以及廠商互相合作幫忙,還有最重要的是大家的支持與陪伴🥹。  
Thumbnail
嘿,大家新年快樂~ 新年大家都在做什麼呢? 跨年夜的我趕工製作某個外包設計案,在工作告一段落時趕上倒數。 然後和兩個小孩過了一個忙亂的元旦。在深夜時刻,看到朋友傳來的解籤網站,興致勃勃熬夜體驗了一下,覺得非常好玩,或許有人玩過了,但還是想寫上來分享紀錄一下~
Thumbnail
模擬世界是我們寫程式造出來的,我們就是模擬世界的主宰,所以各種作用力要長什麼樣子、要怎麼個作用法,都由我們決定。不過,如果希望這些作用力看起來像真實世界的作用力一樣,那在寫程式的時候,套用這些作用力在真實世界中的物理公式,會是比較省時省力的做法。
Thumbnail
這篇內容,將會講解什麼是腳本函式,以及與腳本函式相關的知識。包括腳本的簡介、使用函式(或全域變數)的注意事項、定義全域變數、定義函式、什麼是宣告、局部變數的應用。
Thumbnail
這篇內容,將會講解什麼是變數範圍,以及與變數範圍相關的知識。包括變數範圍的簡介、實體變數、全域變數、局部變數、常數。
Thumbnail
本文介紹了在網站開發中如何運用狀態機的原則和設計方法。通過具體案例分析,以及狀態和數據的區分,詳細介紹了狀態機的設計原則和應用。讀者可以通過本文瞭解如何將狀態機應用於實際的網站開發中。
※ 工廠模式 定義: 工廠模式是一種實現了「工廠」概念的物件導向設計模式。它提供一個通用的工廠介面,將創建instance(實例)的程式碼交由子類別各自實現,並根據需求去動態地生成相應的物件。這種模式將物件的創建邏輯與使用邏輯分開,使程式碼更容易維護和擴展。 特點: 具有高度標準化和同質性的
※ 單例模式介紹 ※ 定義:單例模式是一種設計模式,確保一個class(類)只有一個實例,並提供一個存取它的全域存取點。無論如何取值,皆只對這個實例取值。 ※ 目的:保證一個類別只會產生一個物件,而且提供存取該物件的統一方法。 ※ 講解:單例模式確保一個類無論怎麼 new 或 get,都只能拿
Thumbnail
動態規劃Dynamic Programming其實是 一種泛用的演算法思考方式與演算法建構框架。 動態規劃並不拘束於只能解課本上特定的的範例題。 只要我們能找出DP狀態定義、DP遞迴結構、初始條件(終止條件),就能適用動態規劃來解題,以數學的形式表達,並且在紙筆上或者電腦上、計算機上計算
Thumbnail
完成了Debug.log()的測試,接著還是要跟各位簡單講一下C#的一些規則,之後看程式會(比較)看得懂。 又講到變數? 在Unity中,變數是重要的工具,用來儲存和管理資料。讓開發者能夠靈活調整遊戲的行為和性能,減少代碼的重複性,使得遊戲開發更加高效和簡潔。透過使用變數,開發者可以輕鬆修改資料
Thumbnail
Heuristic 啟發式設計是用戶體驗設計的重要原則之一,涉及了許多與真實世界匹配、用戶控制、一致性和準則等方面,通過一些具體的例子解釋了這些原則的重要性。本文還提供了一些相關的視覺系統狀態和系統與真實世界匹配的例子,同時附帶了一些相關的教程和資料來源。
物件導向設計的一個重點就是封裝,這有很多層面上的意義,但基本上就是控制物件的成員變數和方法的存取權。物件導向的封裝還跟繼承機制有關,這使得有一些時候我們逼不得已必須把函式定義在類別上,這種做法使得物件的功能變得難以拆解。封裝應該是模組的職責,並不需要再給物件相同的能力。 一般的模組系統就是把相