Note
ScriptableObject is a special type of class in Unity that allows you to store large amounts of data independently of GameObjects.
Unlike MonoBehaviour, it is not tied to a scene and does not require an object in the hierarchy.
You can create a ScriptableObject as an asset file in the Project window and store in it:
- numbers, strings, booleans
- lists and arrays
- references to other assets (textures, sounds, models, prefabs)
- custom classes and structs
Create a PlayerStats.asset containing:
maxHealth = 100, moveSpeed = 5.5, jumpForce = 12, weaponPrefab (reference to a sword prefab).
The same ScriptableObject can be used as a configuration file for different scene objects.
This is convenient when you need multiple enemy types, weapons, levels, or graphic settings.
You create an EnemyConfig.asset with fields: health, damage, color, speed.
Now you can create 3 different files:
GoblinConfig.asset(health=30, damage=5)OrcConfig.asset(health=80, damage=15)TrollConfig.asset(health=150, damage=25)
Then in the enemy component (MonoBehaviour), you simply reference the appropriate EnemyConfig and read data from it.
Many developers use singletons (public static GameManager Instance) for global data access.
Drawbacks of singletons:
- hard to test
- tied to a scene
- lost when reloading a scene
- tight coupling between systems
Solution: ScriptableObject as a global service (or event/data manager).
You create a ScriptableObject instance in the project and reference it from any script via a public field (serialization).
This provides:
- loose coupling
- ability to swap implementations (e.g., replace config for testing)
- data persistence between scenes (if you don't reset it)
Example global storage:
// GameDataSO.cs
[CreateAssetMenu(fileName = "GameData", menuName = "Game/GameData")]
public class GameDataSO : ScriptableObject
{
public int score;
public int lives;
public void AddScore(int value) { score += value; }
}Any script can access this data through a reference to GameDataSO assigned in the Inspector.
No Instance needed!
- Create a C# script inherited from
ScriptableObject:
using UnityEngine;
[CreateAssetMenu(fileName = "NewItem", menuName = "Game/Item")]
public class ItemSO : ScriptableObject
{
public string itemName;
public int value;
public Sprite icon;
}- In the editor: right-click in the
Projectwindow →Create→Game/Item→ a.assetfile appears. - Fill in the fields in the Inspector.
- Use in any
MonoBehaviour:
public class ItemPicker : MonoBehaviour
{
public ItemSO itemData; // drag the asset in the Inspector
void Start()
{
Debug.Log(itemData.itemName);
}
}| Approach | Pros | Cons |
|---|---|---|
Fields in MonoBehaviour |
Simple | Data copied to each object, hard to change globally |
| Singleton | Global access | Tight coupling, testing issues |
ScriptableObject |
Asset-based config, replaces singletons, loose coupling | Manual state reset required on game restart |
- 🧹 Reset state: After game ends,
ScriptableObjectfield values (e.g.,score) remain changed because the asset saves to disk. To reset, create aReset()method and call it when the game starts. - 🧩 Events:
ScriptableObjectcan serve as an event bus (Event Channel) — e.g.,VoidEventSO,IntEventSO. Different systems subscribe to the event without knowing each other. - 🧪 Testing: You can create a separate mock
ScriptableObjectfor tests without changing production code.