ScriptableObjects: Data That Lives Outside the Scene
A ScriptableObject is a Unity asset that holds data and logic without belonging to any GameObject, which makes it ideal for shared configuration and decoupled events, provided you understand how differently it persists in the editor and in builds.

Most things in a Unity project live in scenes. Characters, lights, cameras and the scripts attached to them are saved as part of a scene file, created when it loads and destroyed when it unloads. This is natural for things that exist in a place, but a great deal of what a game knows has no place at all. The damage of a sword, the cost of a granary, the list of names from which villagers are christened, the colour of the sky at dusk: none of these belongs to any one object standing in any one scene.
Unity's answer to such placeless knowledge is the ScriptableObject. Like MonoBehaviour, it is a class one derives from in order to write one's own types; unlike MonoBehaviour, it cannot be attached to a GameObject. Instances of it are saved as asset files in the project, alongside textures and meshes, and any number of scenes, prefabs and scripts can refer to the same asset. The data lives once, outside every scene, and is visited rather than copied by whatever needs it.
The idea is simple, and its consequences are wide. ScriptableObjects can reduce memory use, separate design data from code, let designers tune a game without opening scripts, and connect systems that should not know about each other. They also behave in two ways that trap almost everyone at least once: changes made during play in the editor stick, and changes made during play in a build do not. Both follow from what the object actually is, and both are easy to live with once understood.
An object without a body
A ScriptableObject derives from UnityEngine.Object, the same root as GameObject, Material and Texture, which is why it can be saved as an asset, shown in the Inspector, and dragged into reference fields. It has no Transform, no position, no place in the hierarchy. It receives a small subset of the event functions familiar from MonoBehaviour: Awake, OnEnable, OnDisable, OnDestroy and the editor callbacks OnValidate and Reset. It receives no Update, because it is not part of any scene's frame loop.
Fields on a ScriptableObject are saved through the same serialization rules as fields on a MonoBehaviour: public fields and private fields marked SerializeField are written to the asset file and restored when it loads, provided their types are serializable. A WeaponData class might hold a display name, an icon, a damage value, a reload time and a reference to a projectile prefab. Edited in the Inspector, those values are stored in a small text file under the project's Assets folder, readable and diffable in version control.
Methods are allowed too, and this is often forgotten. A ScriptableObject need not be a passive bag of numbers; it can compute derived values, validate its own fields in OnValidate, or provide behaviour that several objects share. A spell asset might contain a method that applies its effect to a target, so that the component casting it simply calls that method without knowing which spell it holds. The asset then becomes a kind of interchangeable cartridge, data and behaviour sealed together in a file.
Making assets with CreateAssetMenu
A ScriptableObject class becomes useful to designers when there is a convenient way to create instances of it. The CreateAssetMenu attribute, placed above the class declaration, adds an entry to the Create menu in the Project window and in the Assets menu. Its optional arguments set the default fileName for new assets, the menuName path under which the entry appears, and an order value that positions it among neighbouring entries. Once it is in place, making a new weapon is a matter of a right click and a name.
Instances can also be made in code. ScriptableObject.CreateInstance, given the type, constructs a new object in memory; a ScriptableObject should be created this way rather than with the new keyword, which does not initialize it properly for the engine. An instance made at runtime exists only in memory unless it is saved, and saving to disk is an editor operation performed through AssetDatabase.CreateAsset. Editor tools sometimes generate whole libraries of assets this way, for example one per row of a spreadsheet of unit statistics.
Organization matters more as asset counts grow. A project with three hundred item assets benefits from a clear menu structure, consistent file names and folders that mirror the game's categories, because designers will navigate these files daily. It also benefits from keeping each type small and specific. A single GameConfig asset holding every number in the game becomes a merge conflict waiting to happen; separate assets for economy, combat and audio let different people tune different things on the same afternoon.
Shared data and the memory it saves
Suppose a prefab for an archer carries a component holding its statistics: health, speed, range, the arrow it fires, a list of voice lines. Spawn two hundred archers, and two hundred copies of those fields exist in memory, identical in every respect. Move the statistics into an ArcherData asset and give the component a single reference to it, and two hundred archers now share one copy. Each instance stores only what is truly its own, such as current health, while the common definition lives once.
This is the flyweight pattern, and ScriptableObjects make it almost effortless. The saving is modest for a handful of numbers and considerable for large arrays, curves or lookup tables. More valuable than the memory, often, is the single point of truth: a designer who decides archers should shoot farther edits one asset, and every archer in every scene and prefab feels the change at once, with no risk that some forgotten copy somewhere still carries the old value.
The shared nature cuts both ways, and the danger is easy to fall into. Because every archer refers to the same asset, writing to a field of that asset at runtime changes it for all of them. A script that subtracts damage from ArcherData.health has not wounded one archer; it has wounded the definition of archers. Shared assets should be treated as read only during play unless the sharing is intended, and per instance state should live on the instance, or in a runtime copy made with Instantiate when an independent version is genuinely needed.
The editor's long memory
In the editor, entering play mode does not copy ScriptableObject assets. The object a script modifies during play is the very asset loaded from the project, the same object the Inspector displays. When play mode ends, scene objects return to their saved state, but the asset does not, because there is nothing to return it to: it was never a temporary copy. A health value lowered during a test remains lowered after the test, and if the asset is later saved, the change is written to disk.
This behaviour is sometimes a gift. A designer can tune values while the game runs, watch the effect, and keep the result without writing it down and re-entering it afterwards, something impossible with ordinary scene components, whose play mode edits are discarded. Many teams build tuning workflows around exactly this property. It becomes a curse when runtime state is stored in assets by accident, and a counter, a list of collected items or a current wave number quietly survives from one test session into the next.
The cure is to separate definition from state. Values meant to be authored live in serialized fields and are only read during play. Values that change during play are kept in fields marked NonSerialized, so they are never written to disk, and are reset explicitly when a session begins, for example by a game manager calling a reset method on each runtime asset at startup. Relying on OnEnable alone for the reset is fragile, because in the editor an asset already loaded may not be enabled again simply because play mode was entered.
What a build forgets
In a built game the situation reverses. ScriptableObject assets are packed into the build's data files, and at runtime they are loaded from there into memory. Scripts may still modify them, and every reader will see the modified values for as long as the asset remains loaded. But nothing writes those changes back. The build's data is not an editable project, and there is no AssetDatabase to save through, so when the game closes and reopens, every asset returns to the values it had when the build was made.
Developers who test only in the editor are sometimes caught by this twice over. A progression system that stores unlocked items in a ScriptableObject appears to work perfectly in the editor, where changes persist between sessions, and fails silently in the build, where every launch begins from nothing. Assets may even be unloaded and reloaded during a session if nothing references them, losing changes sooner than expected. The editor's behaviour, kind as it seems, conceals the truth of the shipped game.
Persistence in a build must therefore be handled deliberately. The usual approach is to serialize the state that matters, for instance with JsonUtility, and write it to a file under Application.persistentDataPath, reading it back at startup and applying it to whatever runtime objects need it. ScriptableObjects remain excellent as the static definitions that such a save refers to: a save file records that the player owns item number seventeen, and the item asset supplies its name, icon and properties.
Event channels and the quiet wire
One of the most influential uses of ScriptableObjects has little to do with data. An event channel is an asset whose only job is to relay a message. It holds a C# event or a list of listeners and exposes a method, often named Raise, that notifies them. A component that wants to announce something, the death of the player or the fall of night, holds a reference to the channel asset and raises it. Components that care hold references to the same asset and subscribe.
The effect is that sender and receivers never refer to one another. The player's health script does not know that a music system, an interface panel and an achievement tracker are listening; they, in turn, do not know who raised the event. Each knows only the asset in the middle. Because the asset lives outside every scene, the connection crosses scene boundaries and survives loading, and a designer can rewire behaviour by dragging a different channel into a slot, without touching code.
The pattern was popularized by a widely watched 2017 Unite conference talk on architecture with ScriptableObjects, and Unity has since described it in its own learning material. Like every indirection, it has a price: following a chain of events through assets is harder than following a direct method call, and a channel nobody listens to fails silently. Listeners must subscribe in OnEnable and unsubscribe in OnDisable, or they will be called after destruction. In a game like Crown & Ashes, where a settlement must respond as one when night falls and the Hunger comes, a single channel raised at dusk can reach every torch, villager and gate that cares.


