【Unity】Unityでデータ駆動設計を実現!ScriptableObjectを活用した効率的なゲーム開発ガイド

作成: 2025-12-10最終更新: 2026-07-10

ScriptableObjectでデータ駆動設計を実現する方法を解説。基本概念からイベントシステム応用まで、実践的なコード例付き。

「ゴブリンのHPを80に調整したい。……あれ、シーンに置いた20体ぜんぶ直すの?」——敵のステータスやアイテム情報をMonoBehaviourのフィールドに直接書いていると、必ずこの壁にぶつかります。データがコピーされてバラバラになり、1回の調整が20回の修正になる。

この問題を根本から解決するのが ScriptableObject(スクリプタブルオブジェクト)です。データを「シーンから独立したアセット」として1つだけ持ち、全員がそれを参照する——調整は1回、反映は全員。この記事では、ScriptableObjectの基本概念から、データ駆動設計、イベントシステムへの応用までを解説します。

ScriptableObjectのイメージ。1枚の光るデータカード(剣とステータス)から糸が伸び、3体のゴブリンが同じカードを共有している

この記事でわかること

  • ScriptableObject=シーンから独立した「データのアセット」
  • [CreateAssetMenu]でのアセット作成とマスターデータ運用
  • 1つのデータを全員で共有——重複・修正漏れ・メモリ無駄の解消
  • よくある間違い——実行時にマスターデータを書き換えない
  • 応用: GameEvent(イベントチャネル)としての活用

Sponsored

ScriptableObjectとは?基本概念の理解

ScriptableObjectとは、Unityにおいてデータのみを保存するために設計された特別なクラスです。MonoBehaviourが「シーン内のオブジェクトの振る舞い」を定義するのに対し、ScriptableObjectは「プロジェクト内の共有データ」を定義します。

最も重要な特徴は、ScriptableObjectのインスタンスがアセットとしてプロジェクト内に保存され、シーンとは独立して存在できるという点です。これにより、データとロジック(MonoBehaviour)を完全に分離し、複数のコンポーネント間で同じデータを効率的に共有できるようになります。

MonoBehaviourとScriptableObjectの役割分担の図。MonoBehaviourはシーンの中の振る舞いを担いシーンに置く、ScriptableObjectはプロジェクトの共有データとしてアセットで存在する

ScriptableObjectの作成手順

ScriptableObjectを作成するには、まずScriptableObjectクラスを継承したC#スクリプトを作成します。そして、Unityエディタのメニューから簡単にアセットを作成できるように、クラス定義の上に[CreateAssetMenu]属性を付与します。

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

// Unityエディタのメニューに「Create/Game Data/Item Data」を追加
[CreateAssetMenu(fileName = "NewItemData", menuName = "Game Data/Item Data")]
public class ItemData : ScriptableObject
{
    // アイテム名
    public string itemName = "Default Item";
    // アイテムの説明
    [TextArea]
    public string description = "A standard item.";
    // アイテムの攻撃力
    public int attackPower = 10;
    // アイテムのアイコン(Sprite型)
    public Sprite itemIcon;
}

このスクリプトを保存した後、UnityエディタのProjectウィンドウで右クリックし、「Create」→「Game Data」→「Item Data」を選択すると、ItemData型の新しいアセットファイル(拡張子は.asset)が作成されます。このアセットが、あなたのゲームの「マスターデータ」となります。

[CreateAssetMenu]属性のmenuNameを工夫することで、プロジェクト内のアセットを整理しやすくなります。例えば、「Game Data/」のように階層化すると便利です。

Sponsored

データ駆動設計の実現

ScriptableObjectの真価は、データ駆動設計(Data-Driven Design)を実現する点にあります。これは、ゲームの振る舞いをコードではなくデータによって制御する設計思想です。

1. データの共有と参照

作成したItemDataアセットを、実際にゲーム内で使用するMonoBehaviourから参照します。

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

public class Item : MonoBehaviour
{
    // ScriptableObjectへの参照
    [SerializeField]
    private ItemData data;

    // アイテムが使用されたときの処理
    public void Use()
    {
        Debug.Log($"{data.itemName} を使用しました。攻撃力は {data.attackPower} です。");
        // 実際のゲームロジック(例:プレイヤーのステータス変更など)
    }

    // 外部からデータを取得するためのプロパティ(オプション)
    public ItemData Data => data;
}

このItemコンポーネントをGameObjectにアタッチし、インスペクターから作成済みのItemDataアセットをドラッグ&ドロップで割り当てます。

MonoBehaviourのインスタンス(GameObject)は複数あっても、参照しているItemDataアセットは一つだけです。これにより、データは共有され、メモリ効率が向上します。

データ共有の比較図。左は3体の敵がそれぞれステータスのコピーを持ち修正が3回必要な状態、右は1枚のScriptableObjectカードを3体が参照し1回の修正で全員に反映される状態

2. データの変更を一元管理

例えば、「剣」の攻撃力を10から15に変更したい場合、ItemDataアセットを一つ修正するだけで、そのアセットを参照しているすべてのItemコンポーネントに修正が反映されます。これが保守性の向上に直結します。

3. よくある間違い

ScriptableObjectのデータを、シーン内のGameObjectのインスペクターで直接上書きしようとすることです。これは、マスターデータそのものを変更してしまうため、意図しない挙動を引き起こす可能性があります。マスターデータは原則として不変(Immutable)として扱い、実行時の状態は別のクラスで管理するのが安全です。

実行時にマスターデータを書き換える危険の図。Play中に攻撃力10を9999へ直接書き換えると、停止しても元に戻らない。実行時の状態は別のクラスで持つこと
Sponsored

応用編:イベントシステムとしての活用

ScriptableObjectは、単なる静的なデータ保存だけでなく、ゲーム内のイベントや状態を管理するための「データコンテナ」としても非常に強力です。これは、特に小~中規模の個人開発において、複雑なイベントシステムを自作する手間を省くのに役立ちます。

GameEventの仕組みの図。発火元がRaiseを呼ぶとGameEventアセットが受け取り、登録された複数のリスナー(音・画面・カウンタ)へ分配される。発火元はアセットしか知らない

GameEventの作成

特定のイベント(例:「プレイヤーがダメージを受けた」「スコアが更新された」)を通知するためのScriptableObjectを作成します。

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

[CreateAssetMenu(fileName = "NewGameEvent", menuName = "Game Data/Game Event")]
public class GameEvent : ScriptableObject
{
    // このイベントを購読しているリスナーのリスト
    private readonly List<GameEventListener> listeners = new List<GameEventListener>();

    // イベントを発火させるメソッド
    public void Raise()
    {
        // リスナーを逆順に回すことで、実行中にリストから削除されても安全
        for (int i = listeners.Count - 1; i >= 0; i--)
        {
            listeners[i].OnEventRaised();
        }
    }

    // リスナーの登録
    public void RegisterListener(GameEventListener listener)
    {
        if (!listeners.Contains(listener))
            listeners.Add(listener);
    }

    // リスナーの解除
    public void UnregisterListener(GameEventListener listener)
    {
        if (listeners.Contains(listener))
            listeners.Remove(listener);
    }
}

GameEventListenerの作成

このイベントを受け取るためのMonoBehaviour(リスナー)を作成します。

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

public class GameEventListener : MonoBehaviour
{
    // 購読するイベントアセット
    public GameEvent Event;
    // イベント発生時に実行するUnityEvent
    public UnityEvent Response;

    private void OnEnable()
    {
        // オブジェクトが有効になったらイベントに登録
        Event.RegisterListener(this);
    }

    private void OnDisable()
    {
        // オブジェクトが無効になったらイベントから解除
        Event.UnregisterListener(this);
    }

    // イベントが発火したときに呼ばれるメソッド
    public void OnEventRaised()
    {
        Response.Invoke();
    }
}

使い方

  1. GameEventアセット(例:PlayerDiedEvent.asset)を作成します。
  2. イベントを発火させたい場所(例:プレイヤーのHPが0になったとき)で、PlayerDiedEvent.Raise()を呼び出します。
  3. イベントを受け取りたいGameObjectにGameEventListenerコンポーネントをアタッチし、EventフィールドにPlayerDiedEvent.assetを割り当てます。
  4. Responseフィールドに、イベント発生時に実行したいメソッド(例:ゲームオーバー画面の表示)をインスペクターから設定します。

この仕組みにより、イベントの発火元と処理元が直接参照し合う必要がなくなり、疎結合で柔軟なシステムを構築できます。

Sponsored

実践:武器データベースを組む

RPGの武器一覧、ローグライトのレリック、タワーディフェンスのタワー種別——「種類がどんどん増えるモノ」の管理は、ScriptableObjectのもっとも気持ちいい使いどころです。ここでは「剣・弓・杖」の3種から始まる武器データベースを組んでみましょう。

武器データベースの図。剣・弓・杖の3枚の武器データアセットがWeaponDatabaseに登録され、ゲーム内のキャラクターに装備として反映される。アセットを足せば武器が増える

まず武器1種=1アセットのデータ型を作ります。

// WeaponData.cs —— 武器1種類ぶんの定義
using UnityEngine;

[CreateAssetMenu(fileName = "NewWeapon", menuName = "Game Data/Weapon")]
public class WeaponData : ScriptableObject
{
    public string weaponName;
    public int attackPower;
    public float attackInterval = 1f;   // 攻撃間隔(秒)
    public Sprite icon;
}

Create > Game Data > WeaponからSword.assetBow.assetStaff.assetを作り、それぞれの数値をインスペクターで入力します。次に、全武器を束ねるデータベースをこれもScriptableObjectで作ります。

// WeaponDatabase.cs —— 全武器のカタログ
using System.Collections.Generic;
using UnityEngine;

[CreateAssetMenu(fileName = "WeaponDatabase", menuName = "Game Data/Weapon Database")]
public class WeaponDatabase : ScriptableObject
{
    public List<WeaponData> weapons;   // インスペクターで武器アセットを登録

    // 名前から武器を引く(見つからなければnull)
    public WeaponData Find(string name) =>
        weapons.Find(w => w.weaponName == name);
}

使う側は、データベース1個を参照するだけです。

public class PlayerAttack : MonoBehaviour
{
    [SerializeField] private WeaponDatabase database;
    private WeaponData currentWeapon;

    void Start()
    {
        // 装備の切り替えは「どのアセットを指すか」を変えるだけ
        currentWeapon = database.Find("剣");
    }

    public void Attack()
    {
        Debug.Log($"{currentWeapon.weaponName}で攻撃! 威力: {currentWeapon.attackPower}");
    }
}

ポイントは2つです。

  • 武器の追加が「アセット作成」で完結する: 4本目の武器「斧」を足すのに、コードは1行も書きません。Axe.assetを作ってデータベースのListに登録するだけ。バランス調整もアセットの数値をいじるだけなので、企画側・デザイナーに調整を任せることもできます。
  • 「所持しているか」はセーブデータ側に持つ: WeaponDataは不変のマスターデータです。「プレイヤーがどの武器を持っているか」は、武器名やIDのListとして JSONセーブ 側に保存し、ロード時にdatabase.Find()で実体を引き直します。この役割分担がデータ設計の背骨になります。

おまけ:先に知っておくと良いこと

  • 実行時の変更はエディタでだけ永続する: エディタ上でPlay中にScriptableObjectを書き換えると、停止後も値が残ります(ビルド版では残りません)。この挙動の違いがバグの温床になるため、実行時の状態は必ず別クラスに持ちましょう。
  • イベントチャネル設計の詳細: 応用編のGameEventパターンをさらに発展させた設計(型付きイベント・デバッグのしやすさ)は ScriptableObjectイベントチャネル設計入門 で深掘りしています。
  • Enum+switchからの卒業: 「アイテムの種類ごとにswitch文」が肥大化してきたら、種類ごとのScriptableObjectアセットに差し替えるサインです。新アイテムの追加が「コード修正」から「アセット作成」に変わります。
  • セーブデータとの関係: ScriptableObjectはマスターデータ(不変の定義)、JSONセーブプレイヤーの状態(可変の進捗)と役割分担するのが定石です。「所持数」はセーブに、「アイテムの定義」はSOに。

まとめ

ScriptableObjectは、Unityにおけるデータ管理と設計の質を劇的に向上させるための鍵となるツールです。この記事で学んだ要点をまとめます。

  • データとロジックの分離: ScriptableObjectはデータのみを保持し、MonoBehaviourから独立させることで、コードの再利用性と保守性を高めます。
  • メモリ効率の向上: データはアセットとして一度だけメモリにロードされ、複数のコンポーネントがそれを共有するため、メモリの無駄を省くことができます。
  • データ駆動設計の基礎: 武器・敵・アイテムのマスターデータをアセットで管理すれば、追加も調整もコード修正なしで行えます。
  • イベントシステムへの応用: GameEventとして活用することで、イベントの発火元とリスナーを疎結合にし、柔軟なシステム構築に役立ちます。

ScriptableObjectを積極的に活用し、よりクリーンで拡張性の高いUnityプロジェクトの実現を目指しましょう。