ScriptableObject 基礎

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
2
3
4
5
6
7
8
[CreateAssetMenu(fileName = "Data", menuName = "ScriptableObjects/SpawnManagerScriptableObject", order = 1)]
public class SpawnManagerScriptableObject : ScriptableObject
{
public string prefabName;

public int numberOfPrefabsToCreate;
public Vector3[] spawnPoints;
}

之後,你可以在 Assets -> Create -> ScriptableObjects -> SpawnManagerScriptableObject 中建立新的 ScriptableObject 實體。

你可以更改該實體的名稱與它的屬性。

接下來你可以在 MonoBehaviour 中使用這個 ScriptableObject 實體,如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
using UnityEngine;

public class Spawner : MonoBehaviour
{
// 要實體化 Prefab 的參考
public GameObject entityToSpawn;

// 一個 ScriptableObject 的參考
public SpawnManagerScriptableObject spawnManagerValues;

// 記錄了實體化了多少個實體
int instanceNumber = 1;

void Start()
{
SpawnEntities();
}

void SpawnEntities()
{
int currentSpawnPointIndex = 0;

for (int i = 0; i < spawnManagerValues.numberOfPrefabsToCreate; i++)
{
// 使用 ScriptableObject 中的資料
GameObject currentEntity = Instantiate(entityToSpawn, spawnManagerValues.spawnPoints[currentSpawnPointIndex], Quaternion.identity);

currentEntity.name = spawnManagerValues.prefabName + instanceNumber;

currentSpawnPointIndex = (currentSpawnPointIndex + 1) % spawnManagerValues.spawnPoints.Length;

instanceNumber++;
}
}
}

在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 按鈕時,會呼叫。

返回 ScriptableObject 系列

Reference: