ScriptableObjects in Unity: The Most Misunderstood Feature

If you’ve been working in Unity for a while, chances are you’ve stumbled across ScriptableObjects — and probably either ignored them or copied some tutorial that used them for data storage without really understanding what they are.

You’re not alone. ScriptableObjects are one of Unity’s most underused, misused, and misunderstood tools. They look like data containers, but they’re actually so much more than that.

Today, we’re going to unpack what ScriptableObjects really are, why Unity created them, what problems they solve, and how they can completely change the way you structure your projects — once you stop treating them like glorified JSON files.

What Are ScriptableObjects, Really?

At their simplest, a ScriptableObject is a Unity asset that stores data and lives outside of your scene hierarchy. You can think of them as serialized C# objects stored as .asset files in your project, not inside a GameObject.

That means they:

  • Don’t need to exist in a scene to be used.

  • Don’t get destroyed when you load or unload scenes.

  • Persist as assets you can reference anywhere in your code or inspector.

Here’s what a minimal ScriptableObject looks like:

Once you create this, Unity lets you generate new .asset files from the Assets → Create → Game → Item Data menu.

You can make dozens of them — each representing a reusable instance of data.

Why Unity Created ScriptableObjects

Before ScriptableObjects, developers faced a few big pain points in Unity:

  1. Duplicated scene data
    You’d have multiple copies of the same values across different prefabs or scenes — item stats, configuration settings, etc.
    Changing one meant hunting them all down manually.

  2. Memory waste and data coupling
    Each prefab carried its own copy of data, meaning more memory usage and more risk of inconsistencies.

  3. Tight coupling between code and scene objects
    Unity encourages scene-based design, but business logic often doesn’t belong inside GameObjects.

ScriptableObjects fix all of that by separating data from behavior and scene instances.

They act as centralized, serialized data assets that you can reference from anywhere. In other words: a ScriptableObject is Unity’s way of saying: “What if we let you store data like prefabs, but without the GameObject baggage?”

ScriptableObjects vs MonoBehaviours

Most developers’ first instinct is to put all logic into MonoBehaviour scripts. But MonoBehaviours live in scenes, while ScriptableObjects live in the project. Here’s the difference in mindset:

The takeaway: MonoBehaviours do things; ScriptableObjects describe things.

You can have a MonoBehaviour reference a ScriptableObject to define how it behaves.

How ScriptableObjects Solve Real Problems

Let’s make this concrete. Imagine you’re building an RPG and you have 50 items: swords, shields, potions, etc. Each one has a name, icon, and damage value.

Without ScriptableObjects, you might create a giant ItemDatabase MonoBehaviour or load JSON files manually. But with ScriptableObjects:

  • Each item becomes its own .asset file.

  • All items share the same C# class.

  • You can reference those assets anywhere in your project.

For example:

Simple ScriptableObject example representing items in a game

Then in your Player script:

Monobehaviour referencing ScriptableObjects

No more giant switch statements or repeated data. Each item is modular, reusable, and lives outside the scene.

The Power of Shared References

The real magic of ScriptableObjects is that they’re shared instances. If you reference the same ScriptableObject in multiple GameObjects, they all point to the same asset in memory. That means when one changes it — every other reference sees that change instantly.

This might sound abstract, but here’s a dead-simple example to prove it.

Example: Shared Player Stats

Let’s say you have a small game where an enemy damages the player, and the UI needs to display the player’s health.

Without ScriptableObjects, you’d have to pass references between scripts, or use a singleton pattern.
But with ScriptableObjects, both systems can read from and write to the same shared data asset.

PlayerStats.cs

A player would have more stats, but I’m simplifying here for the example

EnemyAttack.cs

The player loses 10 health if an enemy makes contact

HealthUI.cs

Note that OnGUI is called multiple times per frame, so it’s very responsive and updates in real time

In this setup:

  • Both EnemyAttack and HealthUI reference the same PlayerStats ScriptableObject asset.

  • When the enemy hits the player, health decreases inside that shared asset.

  • The UI immediately reflects the change — no direct references or event wiring required. HealthUI doesn’t know anything about EnemyAttack, just how we like it. This decoupling allows those classes to not affect each other.

This is the simplest and most visual demonstration of what makes ScriptableObjects special: they turn shared data into a living connection between systems.

Serialization and Persistence

ScriptableObjects are serialized by Unity like assets — meaning their data is saved to disk. But they also play by the same serialization rules as other Unity objects:

  • Public fields and private fields with the [SerializeField] attribute are serialized.

  • References to other assets are saved.

  • Runtime changes to asset instances persist only if you’re in Editor mode.

So if you modify a ScriptableObject at runtime, it won’t overwrite your saved .asset file unless you’re in the Editor and mark it dirty manually. That’s intentional — it prevents accidental data corruption during play.

Common Use Cases

Here’s where ScriptableObjects shine:

1. Data-Driven Design

All item stats, spells, quests, or character data can be defined as assets — easy to edit without touching code.

2. Global Configuration

Settings, difficulty curves, color themes, etc., all live in one centralized place.

3. Event Channels (Decoupling Systems)

Use ScriptableObjects to broadcast game events and keep your systems independent.

4. State Sharing Between Scenes

For example, player stats, inventory, or world state can persist without being tied to a specific GameObject or singleton.

5. Reducing Memory Footprint

Instead of duplicating data across prefabs or components, multiple references can share a single ScriptableObject asset.

Common Misconceptions

❌ “They’re just for storing data.”

Not quite. They can also define behavioral configuration — meaning they don’t just hold raw data, but parameters that influence how systems behave. For example, you might use them to define how aggressive an enemy AI should be, how fast a spell travels, or how your game reacts to difficulty settings. In these cases, the ScriptableObject acts like a behavior profile, shaping logic at runtime without requiring code changes.

❌ “You can’t use them at runtime.”

You absolutely can — they’re perfect for live gameplay data. Just remember to clone them with Instantiate() if you want a temporary instance.

❌ “They’re only for designers.”

They’re designer-friendly, yes, but they’re also one of the best architectural tools available to engineers for decoupling systems.

Advanced Patterns and Tips

✅ Use ScriptableObjects as Event Buses

Replace cross-script references with ScriptableObject “event” assets. They simplify UI ↔ gameplay communication and avoid spaghetti dependencies. This means using ScriptableObjects to carry events between systems instead of relying on direct references or singletons. For example, your UI, audio, and gameplay scripts can all listen to the same ScriptableObject event and react when it’s triggered — without needing to know about each other. This pattern is sometimes called an event channel, and it keeps your architecture clean, modular, and easier to maintain as your project grows.

✅ Use Cloning for Runtime Variants

If you need temporary data, clone an asset:

Now you have a runtime-only version without affecting the original asset.

✅ Editor Tooling

You can make custom editors or validation rules to ensure your ScriptableObjects stay consistent — especially for large databases.

✅ ScriptableObject Singletons

Use a pattern like:

You can store global state without creating a new instance every time

When Not to Use Them

While powerful, ScriptableObjects aren’t a silver bullet. Avoid them if:

  • You need runtime persistence between sessions (use JSON or PlayerPrefs instead).

  • Your data needs to be generated dynamically in massive quantities. ScriptableObjects are best for a manageable number of reusable assets — not thousands of objects created and discarded every frame. If your game needs to spawn large amounts of dynamic data (like hundreds of bullets, particles, or procedurally generated items), using ScriptableObjects for each instance will slow things down and clutter memory. In those cases, you’re better off using plain C# classes or structs created at runtime, which are lighter and faster to allocate.

  • You’re trying to store complex scene-based hierarchies. ScriptableObjects aren’t designed to represent scene hierarchies or nested GameObject structures. They can store references to assets or prefabs, but they can’t serialize things like child transforms, components, or scene-only data (instances of prefabs, transforms/object positions, component states, etc.). If your setup depends on spatial relationships or runtime object references, that’s better handled by prefabs, scene objects, or custom serialization — not ScriptableObjects.

Use them for shared structure, not runtime chaos.

Summary

ScriptableObjects are Unity’s bridge between code and content. They solve:

  • Data duplication

  • Memory waste

  • Tight coupling between code and scenes

They enable:

  • Centralized data

  • Reusable configurations

  • Clean, decoupled architecture

But most importantly, they let you think in data, not just behavior. The moment you stop seeing them as “serialized structs with icons” and start viewing them as reusable system glue, your Unity projects will instantly become cleaner, faster, and easier to scale.

Previous
Previous

Garbage Collection in C#: How .NET Cleans Up After You 🧹

Next
Next

Functional Programming in C#: Thinking in Functions, Not Objects