Using Unity ScriptableObjects for Decoupled Data and Memory Efficiency
Learn how Unity ScriptableObjects eliminate duplicate data, improve memory usage, and let you tweak game values instantly from the Inspector.
23 Aug 2026, 14:29 UTC

The problem: duplicated data wastes memory and makes tweaking tedious
Imagine a wave‑based shooter where every enemy prefab stores its own copy of health, damage, and speed values. If you have 50 enemies in a scene, you end up with 50 separate sets of the same numbers. Changing the base health of all enemies requires editing each prefab or writing a script that pushes the new value at runtime. This duplication also inflates memory usage, especially on mobile targets where every kilobyte counts.
Why ScriptableObjects help
A ScriptableObject is an asset type that lives outside of any scene or GameObject. Multiple components can hold a reference to the same asset, so they all read from a single source of truth. When you edit the asset in the Inspector, every referencing component sees the change immediately.
Key characteristics
- Data is stored once per asset, not per instance.
- Editable in the Unity Editor without touching prefabs or scenes.
- Can be used to define pluggable behaviour (e.g., abilities, weapons) by subclassing a base class and creating asset instances.
Worked example: a shared enemy stats asset
We’ll create a simple EnemyStats ScriptableObject that holds health, damage, and movement speed. Three enemy types will reference the same asset, demonstrating how a single edit propagates to all.
using UnityEngine;
[CreateAssetMenu(fileName = "NewEnemyStats", menuName = "Game/Enemy Stats")]
public class EnemyStats : ScriptableObject
{
public int maxHealth = 100;
public int damage = 20;
public float moveSpeed = 3.5f;
}
Save this script; Unity will add a menu item Assets → Create → Game → Enemy Stats. Create an asset named DefaultEnemyStats and set the values as shown.
Next, a simple enemy MonoBehaviour that reads from the asset:
using UnityEngine;
public class Enemy : MonoBehaviour
{
public EnemyStats stats; // assign via Inspector
private int _currentHealth;
private void Awake()
{
_currentHealth = stats.maxHealth;
}
public void TakeDamage(int amount)
{
_currentHealth -= amount;
if (_currentHealth <= 0) Die();
}
private void Die()
{
Debug.Log($"{name} died");
Destroy(gameObject);
}
}
Attach the Enemy script to three different enemy prefabs (e.g., Goblin, Orc, Knight). In each prefab’s Inspector, drag the DefaultEnemyStats asset into the stats field. Now all three enemies share the same data.
To verify the coupling, select the DefaultEnemyStats asset in the Project window, change maxHealth to 150, and press Play. You’ll see each enemy start with 150 health without modifying any prefab.
Trade‑offs and limitations
- Play‑mode persistence: Values changed while the editor is in Play mode survive after exiting Play mode, which can corrupt test data. Always revert asset changes or use a copy for runtime experimentation.
- Not for save data: In a built player, ScriptableObject assets are read‑only; they cannot persist user progress. Use
PlayerPrefs, serialization, or a dedicated save system for that purpose. - Hidden dependencies: Over‑using global ScriptableObject references can make unit testing harder because components implicitly depend on shared assets. Consider injecting the asset via constructor or interface when testability is a priority.
Actionable steps to adopt ScriptableObjects in your project
- Identify data that is duplicated across many prefabs or scripts (e.g., weapon stats, character abilities, level configuration).
- Create a base ScriptableObject class for that data, adding
[CreateAssetMenu]for easy asset creation. - Replace duplicated fields with a reference to the asset and update any initialization code to read from the reference.
- Edit the asset in the Inspector to verify that all referencing objects update instantly.
- Document any Play‑mode caveats for your team and add a sanity‑check script that warns if asset values are modified during Play mode.
By centralising data in ScriptableObjects you reduce memory footprint, simplify balancing, and keep your prefabs clean—all without writing extra runtime code.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.