【Unity】Achieving Data-Driven Design in Unity: A Comprehensive Guide to ScriptableObject

Created: 2025-12-10

Learn how to achieve data-driven design with ScriptableObject. From basic concepts to event system applications with practical code examples.

Overview

As you progress in Unity game development, you inevitably face the question of how to manage data such as "enemy stats," "item information," and "configuration values."

In many cases, during early development stages, this data tends to be written directly in MonoBehaviour fields attached to GameObjects. However, this approach causes the following problems:

  1. Data duplication: Every time you place multiple enemies with the same stats, the data is copied, wasting memory.
  2. Decreased maintainability: When you want to change data, you need to modify each GameObject one by one, which is time-consuming.
  3. Low reusability: Data and logic become tightly coupled, making it difficult to reuse data in other projects or scenes.

This article thoroughly explains ScriptableObject, a powerful tool for achieving highly reusable and maintainable game design, targeted at beginners to intermediate developers.

What is ScriptableObject? Understanding the Basic Concept

ScriptableObject is a special class in Unity designed to store only data. While MonoBehaviour defines the behavior of objects in scenes, ScriptableObject defines shared data within the project.

The most important characteristic is that ScriptableObject instances are saved as assets within the project and can exist independently of scenes. This allows complete separation of data and logic (MonoBehaviour), enabling efficient data sharing across multiple components.

Steps to Create ScriptableObject

To create a ScriptableObject, first create a C# script that inherits from the ScriptableObject class. Then, add the [CreateAssetMenu] attribute above the class definition to easily create assets from the Unity editor menu.

// Assets/Scripts/ItemData.cs
using UnityEngine;

// Add "Create/Game Data/Item Data" to Unity editor menu
[CreateAssetMenu(fileName = "NewItemData", menuName = "Game Data/Item Data")]
public class ItemData : ScriptableObject
{
    // Item name
    public string itemName = "Default Item";
    // Item description
    [TextArea]
    public string description = "A standard item.";
    // Item attack power
    public int attackPower = 10;
    // Item icon (Sprite type)
    public Sprite itemIcon;
}

After saving this script, right-click in Unity editor's Project window and select "Create" -> "Game Data" -> "Item Data" to create a new ItemData type asset file (extension is .asset). This asset becomes your game's "master data."

By designing the [CreateAssetMenu] attribute's menuName thoughtfully, you can organize assets within the project. For example, hierarchies like "Game Data/" are convenient.

Practical Usage: Achieving Data-Driven Design

ScriptableObject's true value lies in enabling Data-Driven Design. This is a design philosophy where game behavior is controlled by data rather than code.

1. Data Sharing and Reference

Reference the created ItemData asset from MonoBehaviour that actually uses it in the game.

// Assets/Scripts/Item.cs
using UnityEngine;

public class Item : MonoBehaviour
{
    // Reference to ScriptableObject
    [SerializeField]
    private ItemData data;

    // Process when item is used
    public void Use()
    {
        Debug.Log($"Used {data.itemName}. Attack power is {data.attackPower}.");
        // Actual game logic (e.g., modifying player stats)
    }

    // Property for external data access (optional)
    public ItemData Data => data;
}

Attach this Item component to a GameObject and drag & drop the created ItemData asset from the Inspector.

Even with multiple MonoBehaviour instances (GameObjects), they reference only one ItemData asset. This shares data and improves memory efficiency.

2. Centralized Data Change Management

For example, if you want to change the "sword's" attack power from 10 to 15, modifying just one ItemData asset reflects the change in all Item components referencing that asset. This directly improves maintainability.

3. Common Mistake

Attempting to directly overwrite ScriptableObject data in scene GameObjects' Inspectors. This changes the master data itself, potentially causing unintended behavior. Master data should be treated as immutable in principle, and runtime state should be managed in separate classes for safety.

Advanced: Using as an Event System

ScriptableObject is not just for static data storage—it's also very powerful as a "data container" for managing in-game events and state. This is particularly useful in small to medium individual development for saving the effort of creating complex event systems.

Creating GameEvent

Create a ScriptableObject for notifying specific events (e.g., "player took damage," "score was updated").

// Assets/Scripts/GameEvent.cs
using UnityEngine;
using System.Collections.Generic;

[CreateAssetMenu(fileName = "NewGameEvent", menuName = "Game Data/Game Event")]
public class GameEvent : ScriptableObject
{
    // List of listeners subscribing to this event
    private readonly List<GameEventListener> listeners = new List<GameEventListener>();

    // Method to raise the event
    public void Raise()
    {
        // Iterate in reverse order so removal during execution is safe
        for (int i = listeners.Count - 1; i >= 0; i--)
        {
            listeners[i].OnEventRaised();
        }
    }

    // Register listener
    public void RegisterListener(GameEventListener listener)
    {
        if (!listeners.Contains(listener))
            listeners.Add(listener);
    }

    // Unregister listener
    public void UnregisterListener(GameEventListener listener)
    {
        if (listeners.Contains(listener))
            listeners.Remove(listener);
    }
}

Creating GameEventListener

Create a MonoBehaviour (listener) to receive this event.

// Assets/Scripts/GameEventListener.cs
using UnityEngine;
using UnityEngine.Events;

public class GameEventListener : MonoBehaviour
{
    // Event asset to subscribe to
    public GameEvent Event;
    // UnityEvent to execute when event occurs
    public UnityEvent Response;

    private void OnEnable()
    {
        // Register to event when object becomes active
        Event.RegisterListener(this);
    }

    private void OnDisable()
    {
        // Unregister from event when object becomes inactive
        Event.UnregisterListener(this);
    }

    // Method called when event is raised
    public void OnEventRaised()
    {
        Response.Invoke();
    }
}

Usage

  1. Create a GameEvent asset (e.g., PlayerDiedEvent.asset).
  2. Call PlayerDiedEvent.Raise() where you want to raise the event (e.g., when player HP reaches 0).
  3. Attach a GameEventListener component to the GameObject that should receive the event, and assign PlayerDiedEvent.asset to the Event field.
  4. Set the method to execute when the event occurs (e.g., display game over screen) in the Response field from the Inspector.

This mechanism eliminates the need for event source and handler to directly reference each other, enabling loosely coupled and flexible system construction.

Summary

ScriptableObject is a key tool for dramatically improving data management and design quality in Unity. Here are the key points covered in this article:

  • Separation of data and logic: ScriptableObject holds only data, independent of MonoBehaviour, improving code reusability and maintainability.
  • Improved memory efficiency: Data is loaded into memory once as an asset, and multiple components share it, eliminating memory waste.
  • Foundation of data-driven design: Managing master data like items and enemy stats with ScriptableObject enables design where game behavior is controlled by data.
  • Application to event systems: Using it as GameEvent makes event sources and listeners loosely coupled, useful for flexible system construction.

Actively utilize ScriptableObject to aim for cleaner, more extensible Unity projects.