| name | unity-scriptable-objects |
| description | Unity ScriptableObject Architecture |
Unity ScriptableObject Architecture
Use this skill when designing data-driven systems with ScriptableObjects for configuration assets, event channels, shared runtime state, or runtime sets.
Use Cases Overview
| Pattern | Purpose | Example |
|---|
| Config Data | Read-only tuning values | Weapon stats, enemy definitions, level configs |
| Event Channels | Decoupled event bus | OnPlayerDied, OnScoreChanged, OnLevelComplete |
| Shared Variables | Runtime-writable state visible to multiple systems | Player health, score, current weapon |
| Runtime Sets | Track active objects without Find/singleton | Active enemies, active projectiles, spawn points |
| Enum Replacement | Extensible type system | Damage types, surface types, AI states |
Nested SOs and Serialization
[CreateAssetMenu(menuName = "Game/Character Class")]
public class CharacterClass : ScriptableObject
{
public string ClassName;
public WeaponData StartingWeapon;
public List<AbilityData> Abilities;
}
var tempConfig = ScriptableObject.CreateInstance<WeaponData>();
Editor-Only vs Runtime Data
[CreateAssetMenu(menuName = "Game/Level Data")]
public class LevelData : ScriptableObject
{
[Header("Runtime Data")]
[SerializeField] private string _levelName;
[SerializeField] private int _sceneIndex;
[SerializeField] private int _requiredStars;
[Header("Editor-Only Metadata")]
#if UNITY_EDITOR
[TextArea(3, 10)]
[SerializeField] private string _designNotes;
[SerializeField] private bool _isPlaytested;
#endif
public string LevelName => _levelName;
public int SceneIndex => _sceneIndex;
public int RequiredStars => _requiredStars;
}
When NOT to Use ScriptableObjects
| Situation | Better Alternative | Reason |
|---|
| Per-instance mutable state | MonoBehaviour field | SOs are shared; mutating one affects all references |
| Large datasets (1000+ items) | JSON, SQLite, Addressables | Editor slows down with many SO assets |
| User-generated content | JSON/binary serialization | SOs require AssetDatabase (editor-only) |
| Save game data | JSON/binary file | SOs don't persist runtime changes to disk |
| Temporary runtime data | Plain C# class | No need for Unity serialization overhead |
| Configuration that changes per-build | Build scripts / defines | SOs are baked into builds |
Testing with ScriptableObjects
[Test]
public void WeaponDamage_WithArmorReduction_CalculatesCorrectly()
{
var fireDamage = ScriptableObject.CreateInstance<DamageType>();
var config = ScriptableObject.CreateInstance<WeaponData>();
Object.DestroyImmediate(fireDamage);
Object.DestroyImmediate(config);
}
SO Lifecycle Gotchas
-
OnEnable runs in editor -- SOs call OnEnable when loaded in the editor, not just in play mode. Guard runtime-only logic with if (Application.isPlaying).
-
Shared state persists in editor -- If an SO's runtime value is changed during play mode AND the field is serialized, the change persists after exiting play mode. Use [System.NonSerialized] for runtime-only state.
-
OnDisable/OnDestroy timing -- SOs are destroyed when no longer referenced or on domain reload. Don't assume they exist forever.
-
Addressables -- For large projects, load SOs via Addressables instead of direct references to reduce memory footprint and enable asset bundles.
Topic Pages