Imagine developing a Unity supermarket simulation containing hundreds of products.
Each product needs a display name, purchase price, selling price, icon, prefab, category, shelf type, and unlock requirement.
At first, you store those values directly on each product prefab.
That seems reasonable.
Then the game grows.
You add multiple stores, new product categories, upgraded shelves, missions, discounts, and progression systems.
Now the same product information is referenced by shop menus, shelf-placement logic, save data, inventory systems, tutorials, and customer AI.
Updating one value becomes harder because the data is spread across different objects.
This is the kind of problem Unity ScriptableObject Architecture can help solve.
A ScriptableObject is a Unity object that exists independently of GameObjects and can be stored as a project asset. Unity specifically positions ScriptableObjects as useful data containers that can be referenced by multiple objects instead of copying identical values into every prefab instance.
Used carefully, ScriptableObjects can make game data easier to author, reuse, balance, inspect, and hand over to another development team.
Used poorly, they can also create hidden dependencies, unexpected shared state, and confusing save behavior.
The objective is therefore not to convert everything into ScriptableObjects.
The objective is to understand where asset-based data fits into a clean Unity architecture.
Here are ten practical patterns.
1. Separate Game Data From GameObject Behavior
The most important architectural decision is understanding the difference between data and behavior.
A MonoBehaviour is attached to a GameObject.
It is suitable for behavior that depends on the scene or Unity component lifecycle.
Examples include:
- character movement
- collision detection
- camera logic
- animation control
- weapon firing
- vehicle physics
A ScriptableObject does not need to be attached to a GameObject.
That makes it useful for data that should exist independently of a particular scene instance.
Examples include:
- weapon statistics
- product definitions
- vehicle specifications
- character classes
- upgrade costs
- difficulty configurations
- mission definitions
Unity’s current documentation describes ScriptableObjects as assets that can store data independently of GameObjects and be referenced through Inspector fields.
Practical Example
Instead of this:
public class Weapon : MonoBehaviour
{
public string weaponName;
public int damage;
public float fireRate;
public Sprite icon;
}
you can create separate authoring data:
using UnityEngine;
[CreateAssetMenu(
fileName = "WeaponData",
menuName = "Game/Weapon Data"
)]
public class WeaponData : ScriptableObject
{
public string displayName;
public int damage;
public float fireRate;
public Sprite icon;
}
Then the runtime component can reference it:
using UnityEngine;
public class Weapon : MonoBehaviour
{
[SerializeField]
private WeaponData data;
public void Fire()
{
Debug.Log(
$"Firing {data.displayName} for {data.damage} damage"
);
}
}
The weapon behavior still belongs to the GameObject.
The reusable configuration belongs to the ScriptableObject.
That separation is the foundation of a clean data-driven workflow.
2. Use CreateAssetMenu for Designer-Friendly Content
A good architecture should not require programmers to manually create every content object.
Unity provides the CreateAssetMenu attribute for ScriptableObject-derived classes.
When applied, Unity can expose the custom type through the Assets/Create menu so designers can create .asset instances directly from the Editor.
For example:
[CreateAssetMenu(
fileName = "NewProduct",
menuName = "Game/Product Definition"
)]
public class ProductDefinition : ScriptableObject
{
public string productId;
public string displayName;
public int buyPrice;
public int sellPrice;
}
A designer can then create:
- Cola.asset
- Pizza.asset
- Coffee.asset
- Cereal.asset
- ToyCar.asset
without creating additional prefabs simply to hold configuration values.
Why This Matters in Production
This becomes especially valuable when content grows faster than gameplay systems.
A supermarket game might contain 200 products but only one shared shelf-placement system.
A racing game might contain 30 vehicles but only a few controller architectures.
An RPG might contain hundreds of item definitions but only a handful of inventory behaviors.
ScriptableObject assets can allow content creators to work with structured data without editing source code.
Keep Naming Consistent
Do not create assets with vague names such as:
NewData1
WeaponCopyFinal
CarStatsNew2
Prefer clear names such as:
Weapon_AK47
Vehicle_Sports_01
Product_Cereal_Chocolate
Consistent naming makes large projects easier to search and review.
3. Build Reusable Item and Product Definitions
One of the strongest use cases for ScriptableObjects is defining reusable content.
Consider an inventory system.
The same item may appear in:
- a shop
- player inventory
- reward screen
- mission requirement
- save file
- pickup object
- UI tooltip
If every system independently stores the item’s name, price, icon, and properties, inconsistencies can appear.
Instead, one ItemDefinition asset can act as the authoritative authoring record.
Example
using UnityEngine;
[CreateAssetMenu(
menuName = "Game/Item Definition"
)]
public class ItemDefinition : ScriptableObject
{
public string id;
public string displayName;
public Sprite icon;
public GameObject worldPrefab;
public int basePrice;
}
The inventory system can store a reference to the definition.
The shop can display its icon and price.
The pickup object can spawn its world prefab.
The save system can record the stable id.
Why Save the ID Instead of the Asset Reference?
Persistent save data should generally contain reconstructable values rather than relying on scene-specific object references.
GamePro Studio’s existing Unity Save System guide discusses storing stable identifiers and reconstructing gameplay state instead of serializing scene objects directly.
A ScriptableObject definition and a save file therefore solve different problems.
The ScriptableObject defines content.
The save file records the player’s changing state.
4. Keep Runtime State Separate From Authoring Data
This is one of the most important rules in Unity ScriptableObject Architecture.
Suppose you create a character definition containing:
public int health = 100;
If multiple enemies reference the same ScriptableObject and runtime code modifies that shared field, all systems referencing that asset may observe the same changed value.
That may be correct for global shared data.
But it is usually wrong for individual enemy health.
Authoring Data
Good ScriptableObject data:
- maximum health
- movement speed
- damage
- reward amount
- weapon type
- icon
- prefab
- difficulty multiplier
Runtime State
Usually instance-specific:
- current health
- current target
- cooldown timer
- current ammunition
- current position
- status effects
- active mission progress
A runtime enemy component might therefore use:
public class Enemy : MonoBehaviour
{
[SerializeField]
private EnemyDefinition definition;
private int currentHealth;
private void Awake()
{
currentHealth = definition.maxHealth;
}
}
The ScriptableObject provides the starting configuration.
The MonoBehaviour owns the enemy’s changing runtime state.
Why This Separation Matters
It prevents accidental shared-state bugs.
It also makes balancing safer because changing the authored maximum health does not require storing every enemy’s live health inside the same asset.
5. Use ScriptableObjects for Game Balancing
Balancing becomes difficult when configuration values are scattered through scripts and prefabs.
Imagine a racing game containing:
- acceleration
- braking
- steering sensitivity
- maximum speed
- vehicle price
- unlock level
If these values live inside unrelated prefabs, designers may need to open each vehicle separately.
A ScriptableObject-based configuration can make comparison easier.
Vehicle Example
[CreateAssetMenu(
menuName = "Game/Vehicle Definition"
)]
public class VehicleDefinition : ScriptableObject
{
public string vehicleId;
public string displayName;
public float maxSpeed;
public float acceleration;
public float brakingForce;
public int purchasePrice;
}
The vehicle controller can read these values during initialization.
Benefits
This can help teams:
- compare configurations
- create variants
- duplicate definitions safely
- organize content by category
- expose balancing values to designers
- review data during QA
For large projects, custom Editor tooling can improve this further.
For example, a studio might build an Editor window listing all vehicle definitions in one place.
That is an extension of the workflow, not a requirement for using ScriptableObjects.
6. Create Data-Driven Difficulty Configurations
Difficulty settings are another strong use case.
Instead of scattering multipliers throughout several gameplay scripts, define a configuration asset.
For example:
[CreateAssetMenu(
menuName = "Game/Difficulty Profile"
)]
public class DifficultyProfile : ScriptableObject
{
public float enemyHealthMultiplier = 1f;
public float enemyDamageMultiplier = 1f;
public float rewardMultiplier = 1f;
}
You might create:
- Difficulty_Easy
- Difficulty_Normal
- Difficulty_Hard
Gameplay systems can reference the currently selected profile.
Why This Is Better Than Hardcoded Branches
Without configuration assets, code may begin accumulating logic such as:
if (difficulty == Difficulty.Hard)
{
enemyHealth *= 1.5f;
}
A few conditions are manageable.
But as systems expand, difficulty-specific values can spread across enemies, rewards, spawn rates, shops, and mission logic.
A structured configuration asset can centralize the tunable parameters.
Keep Logic in Code
The ScriptableObject should generally define configuration.
The gameplay system should decide how to apply that configuration.
This keeps responsibilities clear.
7. Use ScriptableObjects With Addressables Carefully
ScriptableObjects and Addressables can complement each other, but they are not the same system.
ScriptableObjects provide reusable serialized data assets.
Addressables provide a structured way to identify, load, and release content at runtime.
GamePro Studio’s Unity Addressables Guide explains how Addressables can manage local or remote assets, dependencies, and asynchronous loading.
Example
A vehicle definition might contain lightweight metadata:
- vehicle ID
- name
- price
- statistics
But instead of directly referencing a large vehicle prefab, a larger project might use an Addressable reference.
This can allow the game to load the heavy asset only when needed.
Important Trade-Off
Do not adopt Addressables automatically because ScriptableObjects are present.
For a small game with a limited number of assets, direct references may be simpler.
Use Addressables when the project’s loading, memory, dependency, or content-delivery requirements justify the additional complexity.
Avoid Circular Dependency Thinking
Your architecture should still have clear ownership.
A data asset should not become an uncontrolled container referencing half the project.
Keep responsibilities narrow and understandable.
8. Design ScriptableObjects for Save-System Compatibility
A common misunderstanding is treating ScriptableObject assets as player save files.
That is not their primary runtime persistence role.
Unity’s documentation notes that in a standalone Player, saved ScriptableObject asset data can be read at runtime, but changes made to those assets during gameplay are not automatically written back as persistent project asset changes.
This means a ScriptableObject is suitable for:
- item definitions
- authored values
- default settings
- immutable content descriptions
It should not automatically be treated as the final persistence layer for:
- player coins
- current level
- unlocked vehicles
- mission completion
- current inventory quantities
Better Pattern
Suppose you have:
Vehicle_Sports.asset
with ID:
vehicle_sports_01
The player’s save file might contain:
{
"unlockedVehicleIds": [
"vehicle_sports_01"
]
}
At load time, the game resolves that ID back to the appropriate definition.
This separates:
Game content definition
from
player-owned state
That distinction makes updates and save migration easier to reason about.
The related Unity Save System guide provides a broader persistence workflow including JSON, stable IDs, migration, and corrupted-data testing.
9. Validate Data Before It Reaches Gameplay
Moving configuration into assets creates another opportunity: validation.
If designers can create dozens of ScriptableObject assets, the project should detect invalid data early.
Common Invalid States
Examples include:
- duplicate IDs
- negative prices
- missing icons
- missing prefabs
- zero maximum health
- invalid category references
- inconsistent unlock requirements
Use OnValidate Carefully
ScriptableObject supports OnValidate, which Unity calls in the Editor when relevant serialized values change. The current ScriptableObject API documents OnValidate as an Editor-side callback.
A simple example:
private void OnValidate()
{
if (basePrice < 0)
basePrice = 0;
}
This can prevent certain invalid values.
Do Not Hide Serious Problems
Automatically fixing every invalid field can make mistakes harder to notice.
For serious validation problems, warnings or dedicated validation tools may be better.
Connect Validation to QA
Architecture does not eliminate testing.
Content-heavy games should still verify that:
- references exist
- IDs remain unique
- assets load correctly
- gameplay uses expected values
- builds contain required content
GamePro Studio’s Unity Game QA Checklist provides a broader testing workflow for validating features before client handoff.
10. Build Clear Ownership and Folder Conventions
ScriptableObjects can improve organization only when the project itself remains understandable.
A project containing 500 .asset files without naming rules simply replaces one organizational problem with another.
Example Folder Structure
Assets/
└── Game/
├── Data/
│ ├── Items/
│ ├── Vehicles/
│ ├── Enemies/
│ ├── Difficulty/
│ └── Missions/
│
├── Prefabs/
├── Scripts/
├── Art/
└── Scenes/
Naming Example
Instead of:
Data1.asset
use:
Item_Cola_01.asset
Instead of:
CarSettingsNew.asset
use:
Vehicle_Sports_A.asset
Define Ownership
Each type of data should have a clear purpose.
For example:
ProductDefinition
describes a product.
PlayerInventoryState
tracks what the player currently owns.
ShopSystem
contains purchase and selling behavior.
SaveData
records persistent state.
When these responsibilities are mixed, architecture becomes difficult to maintain.
Think About Outsourcing and Handoff
Clear data architecture is especially helpful when a project changes hands.
A new developer should be able to understand:
- which assets define content
- which components own runtime state
- which IDs are persistent
- where balancing values live
- which systems may safely be modified
This complements the broader production discipline described in GamePro Studio’s 3D Game Asset Pipeline for Unity, where consistent structure, naming, and integration reduce avoidable handoff problems.
Practical Example: ScriptableObject Architecture for a Supermarket Simulator
Consider a hypothetical supermarket simulation.
The game contains 150 products.
Each product needs:
- ID
- display name
- category
- icon
- world prefab
- purchase cost
- selling price
- required store level
Stage 1: Create Product Definitions
Each product receives a ProductDefinition ScriptableObject.
For example:
- Product_Cola
- Product_Cereal
- Product_Cheese
- Product_Pasta
Stage 2: Use Definitions in the Shop
The shop system reads product definitions to display available items.
Stage 3: Use Definitions on Shelves
A shelf slot stores a reference to the product definition and a runtime quantity.
Stage 4: Save Only Runtime State
Instead of serializing entire ScriptableObject assets, the save file records:
- product ID
- quantity
- shelf location
- unlock status
Stage 5: Restore the State
When loading, the game resolves the saved product ID to its corresponding definition.
Stage 6: Balance the Economy
Designers can adjust purchase and selling prices in the definition assets.
Stage 7: Validate Content
A tool or validation step checks for:
- duplicate product IDs
- missing prefabs
- missing icons
- invalid prices
This architecture keeps authored content separate from player progression.
It is an illustrative example, not a documented GamePro Studio client implementation.
Common Unity ScriptableObject Architecture Mistakes
1. Using ScriptableObjects as Global Mutable State Without Planning
Because many objects can reference the same asset, changing shared values at runtime can produce unexpected behavior.
Only use shared mutable state when that behavior is deliberate.
2. Treating ScriptableObjects as a Complete Save System
ScriptableObject assets are useful for authored data, but runtime player progress still needs an appropriate persistence strategy.
3. Putting Everything Into One Giant Data Asset
A single GameDatabase.asset referencing every item, enemy, level, audio file, and prefab can become difficult to maintain.
Split data according to meaningful domains.
4. Mixing Runtime Logic and Content Definitions
Some behavior inside ScriptableObjects can be appropriate, but avoid turning data assets into hidden managers with unclear lifecycle responsibilities.
5. Creating Unstable IDs
If save data references definitions by ID, changing those IDs after release can break compatibility.
Treat persistent IDs carefully.
6. Creating Circular References
Too many interconnected assets can make dependencies difficult to understand.
Keep relationships intentional.
7. Ignoring Asset Loading
A ScriptableObject referencing large prefabs or textures can indirectly keep those dependencies associated with the content path.
For larger projects, evaluate loading architecture separately.
8. Assuming ScriptableObjects Improve FPS Automatically
Their main value is architectural and authoring-related.
Performance benefits depend on the specific data duplication or loading pattern being replaced.
Unity notes that shared ScriptableObject data can reduce duplicate copies of common values, but that does not mean converting arbitrary components into ScriptableObjects will automatically improve frame rate.
ScriptableObject vs MonoBehaviour vs Save Data
| Requirement | ScriptableObject | MonoBehaviour | Save Data |
|---|---|---|---|
| Shared authored configuration | Excellent fit | Possible, but often duplicated | Not primary purpose |
| Scene behavior | Limited role | Strong fit | No |
| Transform / physics interaction | No direct component role | Strong fit | No |
| Item definitions | Strong fit | Possible | Usually stores ID/state only |
| Current enemy health | Usually avoid shared asset | Strong fit | Only if persistence required |
| Player progress | Default/reference data only | Runtime representation | Strong fit |
| Designer-editable assets | Strong fit | Prefab/scene dependent | Usually generated runtime data |
| Runtime persistence between sessions | Not automatic | Not automatic | Designed for this |
This table is a practical architectural guideline rather than a rigid rule.
Different projects may require different trade-offs.
Conclusion
Unity ScriptableObject Architecture is most effective when developers use it to separate reusable authoring data from runtime behavior and persistent player state.
ScriptableObjects can provide a clean foundation for:
- item definitions
- vehicle configurations
- enemy statistics
- difficulty profiles
- mission data
- balancing values
- designer-managed content
However, they should not become a replacement for every MonoBehaviour, manager, save file, or runtime object.
A scalable architecture depends on clear ownership.
Use ScriptableObjects for shared definitions.
Use MonoBehaviours and runtime classes for active gameplay state.
Use dedicated persistence systems for player progress.
When these responsibilities remain clear, large Unity projects can become easier to balance, expand, test, and hand over.
At GamePro Studio, our work covers Unity development, game architecture, optimization, UI/UX, 3D art, and cross-platform production.
Developers planning a new Unity project or restructuring an existing codebase can explore GamePro Studio services or contact GamePro Studio for project-specific requirements.
Frequently Asked Questions
1. What is a ScriptableObject in Unity?
A ScriptableObject is a serializable Unity object that exists independently of GameObjects and can be stored as a project asset. It is commonly used for shared configuration and authoring data.
2. Are ScriptableObjects better than MonoBehaviours?
Neither is universally better. ScriptableObjects are useful for shared data and asset-based configuration, while MonoBehaviours are appropriate for GameObject behavior and scene lifecycle logic.
3. Can ScriptableObjects save player progress?
They should not automatically be treated as a runtime save-file solution. In standalone builds, authored ScriptableObject asset data can be read, but runtime changes are not automatically persisted back to project assets.
4. Can multiple GameObjects reference the same ScriptableObject?
Yes. Sharing one data asset between multiple objects is one of the main reasons ScriptableObjects are useful for common configuration.
5. Are ScriptableObjects useful in mobile and PC games?
Yes. Their architectural usefulness is not limited to one target platform. The decision should depend on the project’s data model, content pipeline, maintainability needs, and runtime architecture.