Reducing Memory Bloat in Unity with ScriptableObject Data Containers
Stop duplicating data across GameObjects. Learn how to use Unity ScriptableObjects to implement a Flyweight pattern, reducing memory overhead and simplifying game balancing.
18 Mar 2026, 21:08 UTC

The Problem: Redundant Data in MonoBehaviors
When building a game with hundreds of similar entities—such as enemies, projectiles, or loot items—developers often store shared properties (like max health, movement speed, or base damage) directly within a MonoBehaviour script. Because every instance of a GameObject creates its own copy of these variables, you end up with hundreds of identical floating-point numbers occupying memory.
This redundancy doesn't just waste RAM; it creates a maintenance nightmare. If you decide the "Goblin" enemy needs a speed increase, you cannot simply change one value; you must either update every prefab or create a complex inheritance chain. The solution is to move shared data into a ScriptableObject, treating the data as an asset rather than a component property.
Decoupling Data from Logic
A ScriptableObject is a data container that exists independently of any scene or GameObject. Unlike MonoBehaviour, it does not need to be attached to a transform to exist. By storing shared constants in a .asset file, multiple GameObjects can reference a single memory address for their configuration data.
This implements a Flyweight Pattern, where the "intrinsic" state (shared data) is stored once, and the "extrinsic" state (current health, position) remains on the individual GameObject. This separation allows designers to tweak game balance in the Unity Inspector during runtime without touching a single line of code.
Practical Implementation: The Enemy Stats System
To implement this, you first define the data structure. Use the CreateAssetMenu attribute to make the object accessible via the Right-Click menu in the Project window.
// EnemyData.cs
using UnityEngine;
[CreateAssetMenu(fileName = "NewEnemyData", menuName = "Game/Enemy Data")]
public class EnemyData : ScriptableObject
{
public float maxHealth = 100f;
public float moveSpeed = 3.5f;
public int attackPower = 10;
}
Next, the MonoBehaviour references this asset instead of defining the values itself:
// EnemyController.cs
using UnityEngine;
public class EnemyController : MonoBehaviour
{
[SerializeField] private EnemyData stats; // Reference to the .asset file
private float currentHealth;
void Start()
{
// Initialize per-instance data from the shared asset
currentHealth = stats.maxHealth;
}
void Move()
{
// Use the shared speed value
transform.Translate(Vector3.forward * stats.moveSpeed * Time.deltaTime);
}
}
Verification Steps
- Create the
EnemyDatascript and create an asset file (e.g., "GoblinStats.asset") in your project. - Attach the
EnemyControllerto ten different GameObjects and assign the same "GoblinStats.asset" to all of them. - While the game is running in the Editor, change the
moveSpeedin the "GoblinStats.asset" file. - Observe that all ten enemies immediately adopt the new speed without needing a restart.
The Trade-off: Persistence and State
The most critical limitation of ScriptableObjects is how they handle data persistence in the Unity Editor. Changes made to a ScriptableObject during Play Mode persist after you stop the game.
If your EnemyController modifies stats.maxHealth directly during a match, that value remains changed in the asset file permanently. To avoid "dirtying" your data, always treat ScriptableObjects as read-only at runtime. If you need to track changes, copy the value from the ScriptableObject into a local variable (as shown with currentHealth in the example) during Start().
Memory and Performance Impact
Using ScriptableObjects reduces the memory footprint by ensuring that only one instance of the data exists in the heap, regardless of how many objects reference it. For a project with 1,000 units, replacing 10 float variables per unit with a single reference pointer can significantly reduce the memory overhead per scene load.
To verify the result, use the Unity Profiler. Compare the "Managed Heap" size when using MonoBehaviour-based variables versus ScriptableObject references. You will notice a decrease in memory allocation as the number of instantiated prefabs increases.
Summary Checklist
- Use ScriptableObjects for: Shared constants, item databases, game configuration, and global event channels.
- Avoid ScriptableObjects for: Per-instance state (e.g., current HP, current position) or data that must reset every single session.
- Safety Check: Ensure you are not writing to the asset file during runtime to prevent accidental permanent data modification.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.