INDEX

Friday, July 31, 2026

ScriptableObjects In Unity And The Hidden Superpowers Most Overlook


When you first encounter ScriptableObjects, it's usually introduced as a way to store data outside of a scene. You create an asset, fill in some values in the Inspector, and suddenly you've got a cleaner alternative to hardcoded variables.

But that's only scratching the surface. It can do more than just hold data!

ScriptableObjects is one of Unity's most powerful and often misunderstood feature. If used correctly, it can simplify your architecture, reduce dependencies, improve reusability, and make your project significantly easier to maintain as it grows.

Unfortunately, it also has a reputation for being overengineered. Spend enough time on Unity forums, and you'll eventually find someone suggesting ScriptableObjects as the solution to virtually every problem. Need an inventory? ScriptableObjects. Dialogue? ScriptableObjects. AI? ScriptableObjects. Coffee machine broken? ...okay, perhaps not that one.

Like any tool, it's at its best when solving the right problem.

Let's deep-dive into what ScriptableObjects really are, when/how/why you should use them, and when it's definitely not the answer.

What exactly is a ScriptableObject?

At its core, a ScriptableObject is essentially an asset that stores data independently from your scenes. If you grew up with cartridge-based consoles, think of a ScriptableObject as a mini game cartridge that you can plug into an enemy, weapon, player, or whatever you want to modify in your game. It comes preloaded with data like health, walk speed, image, sound, weapons, spawn position, etc. 

It's really cool because if you want to see how your object would behave by 'inserting a different mini cartridge,' you can do that by swapping the asset for another one of the same type. It's mainly intended for reading data.

Your save file, however, lives somewhere else. ScriptableObjects are designed in a similar way: they're great for storing data your game reads, but they're not intended to replace a save system.

Unlike a MonoBehaviour, it isn't attached to a GameObject. Instead, it exists as an asset in your Project window.

A basic example might look like this:

[CreateAssetMenu(menuName = "Items/Weapon")]

public class WeaponData : ScriptableObject
{
    public string weaponName;
    public int damage;
    public float attackSpeed;
}

After creating the asset from Unity's Assets menu, you might end up with several different weapons:

  • Iron Sword

  • Steel Sword

  • Flaming Axe

  • Legendary Rubber Chicken

Yes, one of those may be slightly less effective than the others. Or perhaps more effective. It depends on your balancing philosophy.

Each asset contains its own values without requiring duplicate MonoBehaviours.


Why not just use MonoBehaviours?

Imagine you have 200 enemies.

Each enemy contains:

  • Health

  • Movement speed

  • Attack damage

  • Detection range

If every enemy stores identical copies of these values, you're duplicating data unnecessarily.

Instead:

Enemy
    ↓
GoblinData.asset

Enemy
    ↓
GoblinData.asset

Enemy
    ↓
GoblinData.asset

Now every Goblin references the same asset. Change the Goblin's damage once. Every Goblin immediately updates. This saves a tremendous amount of time!

No more hunting through prefabs, wondering which Goblin secretly became the Goblin King.


ScriptableObjects reduce duplication

Let's say we're making a simple RPG, and we're NOT using any ScriptableObjects. We could include:

Sword Prefab

Damage = 20

Attack Speed = 1.2

Sell Price = 50

Weight = 5

Then another sword...

Steel Sword Prefab

Damage = 22

Attack Speed = 1.2

Sell Price = 60

Weight = 5

Then another...

Before you know it, you're maintaining dozens of prefabs whose only real difference is a handful of numbers. Instead, let the prefab handle the behavior, and let the ScriptableObject handle the data!


Separating data from behavior

This is one of the biggest architectural advantages.

Imagine a weapon script:

public class Weapon : MonoBehaviour
{
    [SerializeField]
    private WeaponData weaponData;

    public void Attack()
    {
        Debug.Log($"Attacked for {weaponData.damage} damage.");
    }
}

The Weapon knows how to attack.

The ScriptableObject defines how much damage it deals.

Those are two completely different responsibilities and keeping them separate makes both easier to maintain.


A ScriptableObject isn't just data

Aaaand here's where many developers stop learning...

ScriptableObjects can also contain methods. 

For example:

public class WeaponData : ScriptableObject
{
    public int damage;

    public int GetCriticalDamage()
    {
        return damage * 2;
    }
}
However, it's usually better to keep gameplay behavior inside MonoBehaviours. ScriptableObjects should generally represent reusable assets rather than active game systems.

A good rule is:

Store data in ScriptableObjects.

Execute gameplay in MonoBehaviours.

However, there might be an instance where you'd need to have a method inside a ScriptableObject.

ScriptableObject event channels

Now things get interesting. You can also use ScriptableObjects as event channels.

Instead of this:

Player

↓

UI

↓

GameManager

↓

AudioManager

Everything referencing everything else...

You create an event asset.

For example:

PlayerDamaged.asset

The player raises the event. UI listens. Audio listens. Achievements listen. Nothing needs direct references! Your scripts become significantly less coupled. This pattern has become increasingly popular because it scales remarkably well in larger projects. I see it as a spin-off of EventBuses where the scriptable object acts like a middleman. I wrote an article about Eventbuses 'Decoupling Game Systems in Unity with an EventBus', if you want to know more about it.

Depending on the workflow, one may prefer one over the other.


Runtime data vs configuration data

One mistake beginners often make is modifying ScriptableObjects during gameplay.

Imagine this:

weaponData.damage += 50;

Looks harmless until you quit Play mode. Depending on how you've configured enter Play Mode Options, or whether you're working with editor tools, modifying assets at runtime can lead to unexpected persistence or confusion during development.

More importantly, you're changing the asset itself, not a single weapon instance, you don't want to do that.

If ten enemies share the same ScriptableObject, well, congratulations, you just gave all of them a damage buff. The boss will be delighted. The player... considerably less so. Configuration data belongs in ScriptableObjects. Runtime state usually belongs somewhere else.

It's important to separate ScriptableObjects from objects that live in your scene. What are some good use cases? Let's talk about that below.


Great uses for ScriptableObjects! (examples)

Some systems naturally fit the pattern.

Items

Probably the classic example.

Each asset defines:

  • Name

  • Icon

  • Description

  • Value

  • Weight

  • Rarity


Enemies

Enemy statistics:

  • Health

  • Speed

  • XP

  • Damage


Abilities

Cooldowns.

Mana cost.

Icons.

Animation references.


NPC Definitions

Dialogue portrait.

Name.

Voice.

Biography.

Faction.


Audio definitions

Instead of storing clips everywhere, you can create reusable audio assets. This is very useful for larger projects, and also easier to swap out audio files. Or try a new set of audio clips, and see which one fits your game better.

 


Runtime Sets

Another clever pattern is Runtime Sets. Suppose you want to know every enemy currently alive.

Instead of searching the scene:

FindObjectsOfType<Enemy>()

you maintain a ScriptableObject containing active enemies. Enemies register themselves. Enemies unregister when destroyed. Now any system instantly knows which enemies exist.

No scene searches required.


Dialogue Systems

Dialogue trees are another excellent use case.

Each conversation can become its own asset.

Shopkeeper.asset

↓

Node

↓

Choice

↓

Node

↓

Choice

Designers can edit conversations without touching gameplay code.

Programmers stay happy.

Writers stay happy.

Everyone gets to keep their coffee.


Configuration Assets

One of my personal favorite uses is global configuration.

Imagine:

GameSettings.asset

Holds:

  • Player speed

  • Gravity

  • XP curve

  • Starting gold

  • Difficulty multipliers

Every system references one asset and balancing suddenly becomes much easier.



ScriptableObjects and memory

Another benefit often overlooked is memory efficiency.

Imagine 500 enemies sharing one ScriptableObject.

Instead of duplicating all configuration data 500 times...

Unity loads one asset.

Every enemy references it.

That's significantly cleaner.

Of course, don't expect dramatic memory savings from a handful of integers alone. The bigger benefit is centralization and maintainability, with memory efficiency being a welcome bonus.


Common mistakes

Some pitfalls appear again and again, like using ScriptableObjects for everything. Not every class needs to become an asset. Sometimes a normal C# class is exactly what you need. Another thing is, treating it like save data. Not a good idea, with save data you write to the ScriptableObject - although possible, they're intended for read-only.

Storing runtime references

This often creates confusing bugs. For instance, scene objects can disappear and assets remain. Now you have invalid references.

Forgetting They're Shared

Perhaps the most common mistake. Every object referencing the asset sees the same data. That's a feature...

A Practical Example

Imagine you're building a fantasy RPG and you have:

  • 40 weapons

  • 25 enemies

  • 60 abilities

  • 15 consumables

Without ScriptableObjects, every prefab contains duplicated configuration data. Updating a sword's damage means editing multiple prefabs. Updating every goblin means opening several assets.

Now imagine everything references shared ScriptableObjects instead. Need to rebalance all goblins? Just open one asset and increase their health from 50 to 65.

Done.

Your prefabs stay focused on behavior, and your assets stay focused on configuration.

Your future self will silently thank your past self! 



What ScriptableObjects are terrible at

Like every design pattern, there are situations where ScriptableObjects aren't ideal. The biggest one to keep in mind: WRITING to the SO. Here are some examples:

Player Health

Don't store the player's current health inside a ScriptableObject asset.

That's runtime state. Use a normal class or MonoBehaviour instead.

Temporary Variables

Kind of similar of what I mentioned above, but just to reiterate: Things that constantly change usually don't belong inside assets.

Examples:

  • Ammo count

  • Current score

  • Active quest progress/scene references

  • Current animation

     

Avoid storing scene-specific GameObjects inside ScriptableObjects. Those references easily become invalid across scenes.

Another thing is, make sure your ScriptableObject has all the code written out that you want. When you fill in all the values and come up with something else you wanted to add to your ScriptableObject, it will erase all the fields that you've filled in!

 


Should every project implement it?

Depends. With smaller, beginner projects, it's likely unnecessary. 

ScriptableObjects shine whenever you have shared, reusable configuration data that many objects need to reference.

If you're storing temporary gameplay state, rapidly changing values, or information unique to a single object instance, a regular class or MonoBehaviour is usually a better fit.

Like many architectural decisions in Unity, the trick isn't finding a feature you can use everywhere; it's recognizing the situations where it genuinely simplifies your code.


What it all means in the end:

ScriptableObjects can do more than just contain data. They're a powerful way to separate configuration from behavior, reduce duplication, and build systems that are easier to maintain as your game grows.

Whether you're defining weapons, abilities, enemy stats, dialogue trees, audio definitions, or global configuration, they encourage a cleaner architecture by allowing multiple objects to share the same source of truth.

That doesn't mean we found the holy grail. It isn't designed to replace every class in your project, and they shouldn't be used to store constantly changing runtime state.

Use them where they make sense. Keep behavior in your components, keep shared configuration in assets, and resist the temptation to turn every problem into a ScriptableObject just because it's possible.

As with most aspects of game development, the best architecture isn't the most clever, it's the one that's easiest to understand six months later, when you've forgotten why you thought naming an overpowered weapon "Legendary Rubber Chicken" was a perfectly sensible idea.

No comments:

Post a Comment