ScriptableObject 是一個資料容器(Data container),用於儲存資料並作為資源(Asset)在執行時使用。除了基本的資料儲存功能外,它還有多種特殊用法。
ScriptableObject 是一個資料容器(Data container),用於儲存資料並作為資源(Asset)在執行時使用。除了基本的資料儲存功能外,它還有多種特殊用法。
ScriptableObject as delegate object
ScriptableObject 除了作為資料的容器外,還可在其內部定義方法,讓 MonoBehaviour 可以將自己傳入 ScriptableObject 的方法中,根據不同的 ScriptableObject 實體去執行不同的動作。
例如:要做增益效果,定義一個抽象的 PowerupEffect 類,它繼承 ScriptableObject ,並且只有一個 ApplyTo 方法,這個方法將增益效果應用到傳入的 GameObject 中。
1 | public abstract class PowerupEffect : ScriptableObject |
建立一個子類 HpBooster ,並 override 了 ApplyTo ,將增加遊戲物件的生命值。
1 | public class HpBooster : PowerupEffect |
在建立一個 增益物件 的腳本,這個增益物件將持有 PowerupEffect 的參考,當在觸發器中觸發時,就會呼叫它持有的 PowerupEffect 來將增益效果應用到碰到的遊戲物件。
1 | public class Powerup : MonoBehaviour |
例子,使用音效
1 | // 這個 struct 為最大最小值的結構 |
定義一個抽象的 AudioDelegateSO 並繼承 ScriptableObject。在其中含有一個 Play 抽象方法,讓傳入的 AudioSource 播放音效。
1 | public abstract class AudioDelegateSO: ScriptableObject |
定義一個 SimpleAudioDelegateSO 類,它繼承 AudioDelegateSO ,
這個 SimpleAudioDelegateSO 可以管理一個音效列表clips,在 Play 方法中,將隨機播放 clips 列表中的聲音。
1 | [] |
我們可以使用這個 SimpleAudioDelegateSO 讓同一個遊戲物件隨機播放不同的聲音,例如可以儲存狗的叫聲,每當觸碰狗遊戲物件時,就隨機播放一種叫聲。
例子: 在遊戲中,會分為玩家控制,或是電腦控制的遊戲物件,如坦克遊戲中的坦克,可以由不同 AI 控制,也可以為玩家控制。這種不同的行為,我們也可以用 Delegate Object 來做。
定義一個抽象的 TankBrain 繼承 ScriptableObject。其中
Initialize 虛方法,不一定會實作這個方法,但是當需要做初始化的動作時可以 override 它。Think 抽象方法,決定傳入的 TankThinker 要有什麼行為。1 | public abstract class TankBrain : ScrptableObject |
定義一個 PlayerControlledTank 類,它繼承 TankBrain
override Think 方法,在裡面取得坦克的位置,並判斷輸入來移動坦克或是發射砲彈。1 | [] |
定義一個 SimpleSniper 類,它也繼承 TankBrain ,它為電腦操控的坦克。
override Think 方法,在裡面判斷是否移動與開火Remeber)中判斷是否看過玩家坦克Remeber)中沒有玩家坦克,則找最近的物件,並判斷是否為玩家坦克1 | [] |
Reference:
將 ScriptableObject 作為 Enum 可以有很多好處,像是可含有自己的屬性和方法等。另外還不具 Enum 的一些缺陷
Enum 的缺陷有
它會隱含(implicitly)的為所有元素賦予一個整數值,這造成刪改 Enum 中的值很不方便,若隨意刪除一個元素,那它之後元素的值都會變
1 | enum Skill |
預設會自動幫你的賦值
1 | enum Skill |
除非使用 Nullable 否則它不可以為 NULL ,因此一般常會自訂一個元素來代表空值
1 | enum Skill |
在 C# , enum 中不允許有方法或是屬性,需要使用擴展方法來完成
1 | enum Skill |
1 | public static class SkillExtensions |
因此將 ScriptableObject 作為 Enum 來使用也是一種好選擇,使用的方式為
1 | [] |
然後在透過 Unity 編輯器建立需要的元素即可
使用上也和 Enum 類似
Reference:
ScriptableObject as Runtime Set
RuntimeSet 的做法是將 ScriptableObject 作為一個分享集合容器,在執行期間,將物件放入,讓其他要使用它的物件可以透過這個分享容器拿到。
以下的例子中,將建立一個 GameObjectRuntimeSet ,這個 GameObjectRuntimeSet 將會在執行時持有 Circle 物件集合,而 Circle 物件將由 CreateObject 建立,而 ObjectMonitor 負責查看 GameObjectRuntimeSet 中有多少 Circle 物件
以下建立一個通用抽象類 RuntimeSet 它繼承 ScriptableObject
1 | public abstract class RuntimeSet<T> : ScriptableObject |
接著繼承 RuntimeSet 建立一個 GameObjectRuntimeSet , 這個 GameObjectRuntimeSet 將為 GameObject 的分享集合容器
1 | [] |
在 Unity 編輯器中建立一個 ScriptableObject 實體 CircleRuntimeSet
接著建立要放到這個集合的物件 Circle ,在 OnEnable 時將自己加入集合, OnDisable 時將自己從集合中移除
1 | public class Circle : MonoBehaviour |
把 ScriptableObject 實體 CircleRuntimeSet 設定到 Circle 的 GameObjectRuntimeSet 中,讓 Circle 知道在 OnEnable 時要放入哪個集合。之後,把 Circle 轉為 Prefab
再建立一個製造Circle的 CreateObject,這邊建立一個按鈕,當按下時,會觸發 CreateObject.Create()
1 | public class CreateObject : MonoBehaviour |

最後建立一個觀看這個集合的物件
1 | public class ObjectMonitor : MonoBehaviour |
觀察結果
每按下按鈕一次,所產生的 Circle 物件都會加入 GameObjectRuntimeSet ,而需要知道有多少個 Circle 的物件,它只要從 GameObjectRuntimeSet 裏面就可以找到目前有多少 Circle 物件。
總結
RuntimeSet 將 ScriptableObject 作為分享的集合容器。Object.FindObjectOfType 或 GameObject.FindWithTag 等開銷可能會比較大的方法來搜尋物件Reference:
ScriptableObject 可以用來作為事件系統的一部分。這種方法可以幫助我們在不同的 MonoBehaviour 之間進行通信,而不需要它們彼此知道對方的存在,從而實現鬆耦合。
首先要建立 GameEvent 與 GameEventListener 。它們兩個互相依賴
GameEvent
GameEventListener 列表用來保存要聽取該事件的聽眾,RegisterListener 來註冊聽眾UnregisterListener 將聽眾移出Raise 觸發聽眾註冊的事件1 | [] |
GameEventListener
Event ResponseOnEnable 中將自己註冊到 Event 中OnDisable 中將自己從 Event 中移出OnEventRaised 中呼叫回應的方法1 | public class GameEventListener : MonoBehaviour |
接著在 Unity 編輯器中建立一個 ScriptableObject 實體,並命名為 ClickEvent
建立一個 DoSomethingWhenClick 的遊戲物件(GameObject) ,並添加以下腳本,這個遊戲物件有一個公開方法 JustDoIt,當事件發生時,要被呼叫。
1 | public class DoSomethingWhenClick : MonoBehaviour |

建立一個 EventListener 的 GameObject,將 GameEventListener 腳本添加到這個 EventListener GameObject 中。
ScriptableObject 實體 ClickEvent ,拖放到,EventListener 中的 Event 欄位,表示這個 EventListener 要監聽的事件是 ClickEventDoSomethingWhenClick 遊戲物件放到 Response 欄位,並設定要執行該物件的公開方法 JustDoIt
接著建立事件發起的物件,這邊建立兩個, RaiseClickableCircle 與 RaiseClickableButWithUnityEvent
RaiseClickableCircle 的腳本如下,它主要是直接使用 GameEvent 作為參考,其缺點是只能用一個 Event
1 | public class RaiseClickableCircle : MonoBehaviour |

RaiseClickableButWithUnityEvent 幾乎與 RaiseClickableCircle 一模一樣,差別在於是使用 UnityEvent ,這樣可以放置多個 Event,當觸發 Raise 時在裡面的 Event 都會觸發。
1 | public class RaiseClickableButWithUnityEvent : MonoBehaviour |

最後執行,並點擊畫面上的圓形或是方形,可以發現都會觸發 DoSomethingWhenClick 的 JustDoIt 方法,而 RaiseClickableButWithUnityEvent , RaiseClickableCircle 與 DoSomethingWhenClick 完全不知曉對方的存在。
使用基於事件架構(event-based architecture)的好處是,它不會每一幀都在執行,只有在當事件發生時才會執行,因此效率會比在 MonoBehaviour 的 Update 方法中執行好。
Reference:
ScriptableObject as shared variable
ScriptableObject 可以作為遊戲變數在各個 MonoBehaviour 之間分享資料,此外,透過這種方式還可以減少 MonoBehaviour 物件之間互相參考的耦合度。
下面將使用 ScriptableObject 建立玩家生命數值,並在不同的 MonoBehaviour之間使用
首先建立一個繼承 ScriptableObject 的類 FloatVariable
1 | [] |
接下來建立兩個 FloatVariable 實體, HP 與 MaxHP

然後建立繼承 MonoBehaviour 的 Player 類
1 | public class Player : MonoBehaviour |
在 Unity 編輯器中,將剛剛建立的 HP 與 MaxHP 設定到 Player 上
接著建立一個 HPView
1 | public class HPView : MonoBehaviour |
在 Unity 編輯器中,將 HP 與 MaxHP 設定到 HPView 上,
將 Player.Hurt() 設定給按鈕的 On Click 。
最後執行,在畫面上可以按下按鈕,觸發 Player.Hurt() ,可以觀察到 CurrentHPText 的變化
並且可以發現這兩個 MonoBehaviour: Player 和 HPView 互相不知道彼此的存在,實現了鬆耦合(loosely coupled)。
Reference:
ScriptableObject 是一個資料容器(Data container),它透過共享通用資料的方式來減少對重複資料的記憶體佔用,進而降低你APP的記憶體使用量。
如果你的預製物件(Prefab)中儲存了一些不會改變的資料,那麼很適合把它們改為使用 ScriptableObject ,因為每當你實體化(instantiate)一個預製物件(Prefab)時,這個實體會保存自己的一份資料,當實體化很多個實體時,會有很多重複的資料佔用很多記憶體,因此你可以使用 ScriptableObject 可以把資料儲存起來,然後讓所有的預製物件(Prefab)存取這個 ScriptableObject 。
ScriptableObject 和 MonoBehaviour 一樣,繼承了 UnityEngine.Object ,但是與 MonoBehaviour 不同在於你不能把 ScriptableObject 附加(attach)到一個 GameObject 上,也不能進行 GatComponent 等對 GameObject 的操作,相反你需要把它儲存為資源(Asset)。
在使用Unity編輯器時,你可以把資料放在 ScriptableObject ,在執行時也可以調整其中的資料,但是你不可以把它當作遊戲存檔的工具。
使用 ScriptableObject 主要情況是在編輯時期修改 ScriptableObject 的資料,在執行時這些資料會被當作資源(Asset)使用。
建立一個類並繼承 ScriptableObject,你可以使用 CreateAssetMenu 屬性(attribute),讓你方便在編輯器中使用,以下是一個範例:
1 | [] |
之後,你可以在 Assets -> Create -> ScriptableObjects -> SpawnManagerScriptableObject 中建立新的 ScriptableObject 實體。
你可以更改該實體的名稱與它的屬性。
接下來你可以在 MonoBehaviour 中使用這個 ScriptableObject 實體,如下
1 | using UnityEngine; |
在Unity編輯器中,使用拖拉的方式將剛剛建立好的 ScriptableObject 實體拖拉到對應的位置上,另外 Box Prefab 是一個簡單的Square
最後在執行Unity,就可以看到Unity使用剛剛建立出來 ScriptableObject 資料去產生 Box 物件。
此外除了在編輯器中建立 實體,在執行期間也可以透過 CreateInstance 來建立。
1 | ScriptableObject.CreateInstance<MyScriptableObjectClass>(); |
MonoBehaviour 與 ScriptableObject 的比較
| MonoBehaviour | ScriptableObject |
|---|---|
MonoBehaviour 接收所有來自 Unity 的 callback,像是 Start , Awake , Update , OnEnable ,OnDisabl , OnCollisonEnter 等 |
ScriptableObject 只接收一些: Awake , OnEnable , OnDestroy 與 OnDisable 。 在 Editor 中的話還有 OnValidate 與 Reset |
MonoBehaviour 必須要 附加(attach)到 GameObject 上 |
ScriptableObject 不能附加到 GameObject 上,需要在專案(Project Level)中將它們儲存為 asset 檔案,在其他腳本中參考這些 ScriptableObject asset |
當儲存 MonoBehaviour 時,會將它們的資料儲存到 Scenes 與 Prefabs 中 |
每個 ScriptableObject 實體都會被保存為專案層級(Project level)的獨立檔案中 |
一般在 Play Mode 中修改 MonoBehaviour 中的值之後離開 Play Mode 的話,這些值會被重新設定(Reset) |
當離開 Play Mode 後, ScriptableObject 則不會重新設定。此外當編譯發布之後,執行期間 ScriptableObject 修改的值不會被保存,而是為發布時的值 |
在 ScriptableObject 中只有以下的 callback 會被呼叫
Awake : 與 MonoBehaviour 的類似,只有在 ScriptableObject 開始時會被呼叫。當遊戲執行(launched)或是有參考到這個 ScriptableObject asset 的 Scene 被載入時會呼叫。OnEnable : 在 Awake 之後呼叫,當 ScriptableObject 被載入(Loaded)或是實體化(Instantiated)時會被呼叫。當 ScriptableObject.CreateInstance 或是 script 被重新編譯(recompilation)時 OnEnable 會被呼叫。OnDisable : 當載入的 Scene 不再參考這個 ScriptableObject asset 時,就會呼叫 OnDisable , 它會在 OnDestory 之前被呼叫。此外在 script 重新編譯進入 Play Mode 時,也會呼叫,此時會出現 OnDisable 出現在 OnEnable 的情況。 OnDestory : 當 ScriptableObject 在 編輯器中刪除,或是重程式碼中刪除時,會呼叫 OnDestory 。如果是在執行期間建立的 ScriptableObject 在APP離開或是離開 Play Mode 時也會呼叫。以下 callback 只在編輯器中呼叫
OnValidate : 當值在 Inspector 中改變值,才會呼叫。可以在這邊確保你的編輯器輸入的資料是合適的範圍(如 0 ~ 1)。Reset : 當點擊 Inspector 中的 Reset 按鈕時,會呼叫。Reference: