スポンサーリンク
UnityUnityメモ

Unityのデータ駆動設計入門|ScriptableObjectでハードコーディングから卒業する方法

Unity

Unityでゲームを作っていると、最初はシンプルだったはずのコードが、気づけば修正のたびに不安になる状態になっていませんか?

敵のHPを少し変えたいだけなのにスクリプトを開いて数値を書き換え、影響範囲が怖くてテストに時間がかかる。 Singletonが増えすぎて「これ、どこから呼ばれてるんだっけ?」と頭を抱える……。 実はこれ、Unity開発でとてもよくある“成長痛”なんです。

その原因の多くは、ロジックとデータが強く結びついたまま(ハードコーディングされたまま)設計が進んでしまうことにあります。

この記事では、そうした悩みを解決する考え方として「データ駆動設計」を取り上げ、 Unityで実践しやすい形に落とし込んで解説していきます。

中心となるのは ScriptableObject。 「名前は知っているけど、正直Prefabと何が違うの?」 「使ってみたけど、設計として合っているのか自信がない」 そんな状態から一歩抜け出すことを目標にします。

この記事を読み終える頃には、

  • なぜハードコーディングが問題になりやすいのか
  • ScriptableObjectを使うと何がどう改善されるのか
  • 依存を増やさずに拡張していく設計の考え方

これらが、感覚ではなく言葉と構造で説明できるようになります。

小規模なプロジェクトでも、中〜大規模を見据えた開発でも役立つ内容なので、 「今の設計、ちょっと不安かも…」と感じているなら、ぜひこのまま読み進めてみてくださいね 🙂


  1. 1. なぜUnity開発はハードコーディング地獄に陥るのか
    1. データがあちこちに散らばる
    2. 修正=コード変更になりがち
    3. Singletonが増えて依存関係が絡まる
    4. チーム開発ではさらに問題が大きくなる
  2. 2. データ駆動設計という考え方
    1. データ駆動設計とは何か
    2. 「値を変える=コードを書く」状態からの脱却
    3. Unityとデータ駆動設計の相性
    4. なぜScriptableObjectが向いているのか
  3. 3. ScriptableObjectで始める基本的なデータ駆動設計【実践】
    1. Step1. ScriptableObjectクラスを作成する
    2. Step2. データアセットを作成する
    3. Step3. MonoBehaviourからデータを参照する
    4. データを共有できることの強さ
    5. データが増えてきたときの悩み
  4. 4. ScriptableObjectイベントで疎結合な設計にする
    1. なぜイベントで設計すると疎結合になるのか
    2. GameEvent(発信側)を作る
    3. GameEventListener(受信側)を作る
    4. イベントアセットを作ってつなぐ
    5. イベントを発火する
    6. この設計のメリット
  5. 5. 実行時データと保存データをどう扱うか
    1. ScriptableObjectは「定義データ」として扱う
    2. 実行中にScriptableObjectを変更するとどうなるか
    3. セーブデータが絡むと一気に難しくなる
    4. 保存処理を安全に任せたい場合
  6. 6. さらに一段上の設計を目指す人へ(設計思考・ECS)
    1. データ駆動設計とECSの関係
    2. 「設計の引き出し」を増やすという考え方
    3. 設計そのものを学びたい人へ
  7. まとめ
  8. あわせて読みたい
    1. 参考文献
  9. よくある質問(FAQ)
    1. 関連記事:

1. なぜUnity開発はハードコーディング地獄に陥るのか

Unityで開発を始めたばかりの頃は、
「とりあえず動くものを作る」ことが最優先になりますよね。

敵のHPは int hp = 100;
攻撃力は int attack = 10;
まずは MonoBehaviour に直接書いてしまう。 これはごく自然な流れです。

問題が表に出てくるのは、仕様変更や要素追加が増えてきたタイミングからです。

データがあちこちに散らばる

同じ能力を持つ敵を複数配置したとき、 それぞれの MonoBehaviour に同じ数値を書いていると、こんな状態になります。

  • ちょっとHPを調整したいだけなのに、修正箇所が多い
  • 一部だけ修正し忘れて、挙動がズレる
  • 「この数値、どれが正解だっけ?」となる

データがコードと一緒に分散して埋め込まれている状態は、 後から見返したときにとても把握しづらいんです。

修正=コード変更になりがち

ハードコーディングされた設計では、 数値を変える=コードを触ることになります。

するとどうなるかというと、

  • 小さな調整でも再コンパイルが必要
  • 影響範囲が読めず、修正が怖くなる
  • 試行錯誤のテンポが一気に落ちる

特にゲームバランス調整は、
「ちょっと変えて、すぐ試す」を何度も繰り返したい場面ですよね。

ここで足かせになるのが、コードに直結したデータ管理です。

Singletonが増えて依存関係が絡まる

状態管理や共通データを扱うために、 Singletonを使ったことがある人も多いと思います。

最初は便利に感じるのですが、数が増えてくると、

  • どのクラスがどこに依存しているのか分からない
  • テストや差し替えが難しい
  • 一部を直しただけで別の機能が壊れる

いわゆる密結合の状態になりやすく、 「触るのが怖いコード」へと成長してしまいます。

チーム開発ではさらに問題が大きくなる

もしデザイナーやプランナーと一緒に作業する場合、

  • 数値調整のたびにプログラマが呼ばれる
  • ちょっとした変更で作業が止まる

こんな状況にもなりがちです。

これらの問題は、スキル不足ではなく、 設計の段階で「データの扱い方」を決めていないことが原因で起こります。

次の章では、こうした問題をまとめて解消できる考え方として、 データ駆動設計を紹介していきます。




2. データ駆動設計という考え方

先ほど見てきた問題の多くは、
「データ」と「処理(ロジック)」が分離されていないことから生まれています。

そこで登場するのが、データ駆動設計という考え方です。

データ駆動設計とは何か

データ駆動設計をひとことで言うと、

「処理はコードに書き、内容はデータとして外に出す」設計

という考え方です。

敵のHPが100か200か、
攻撃力が10か25か、
そういった「変わりやすい情報」をコードから切り離します。

コード側は、

  • HPという概念がある
  • ダメージを受けたら減る
  • 0になったら倒れる

といった振る舞いだけを担当します。

「値を変える=コードを書く」状態からの脱却

ハードコーディングされた設計では、 数値を変えたいだけなのにスクリプトを開く必要がありました。

データ駆動設計では、

  • 値の変更はデータ側
  • 挙動の変更はコード側

という役割分担がはっきりします。

これにより、

  • コードを壊さずにバランス調整できる
  • 再コンパイルなしで試行錯誤できる
  • 「この変更、危なくない?」という不安が減る

といったメリットが生まれます。

Unityとデータ駆動設計の相性

Unityはもともと、 エディタで値を調整しながら作ることを前提にしたエンジンです。

Inspector、Prefab、SerializeField…… これらはすべて「データを外から差し替える」ための仕組みとも言えます。

その中でも、 データ駆動設計の中核として使いやすいのが ScriptableObjectです。

なぜScriptableObjectが向いているのか

ScriptableObjectを使うと、

  • データをアセットとしてプロジェクトに保存できる
  • 複数のオブジェクトから同じデータを参照できる
  • Prefabやシーンに依存しない

といった特徴を活かせます。

これはまさに、

「ロジックから独立した、使い回せるデータ定義」

を作るための仕組みです。

次の章では、 ScriptableObjectを使って基本的なデータ駆動設計をどう実装するのかを、 具体的な手順ベースで見ていきましょう。




3. ScriptableObjectで始める基本的なデータ駆動設計【実践】

ここからは、ScriptableObjectを使った 基本的なデータ駆動設計の実装手順を見ていきます。

例として、 「敵キャラクターのステータス」をデータとして管理するケースを想定します。

Step1. ScriptableObjectクラスを作成する

まずは、データの器となるScriptableObjectを定義します。

プロジェクトウィンドウを右クリックし、 「Create」→「C# Script」を選んで新しいスクリプトを作成し、 名前を EnemyData とします。


using UnityEngine;

[CreateAssetMenu(menuName = "Game Data/Enemy Data")]
public class EnemyData : ScriptableObject
{
    public string enemyName;
    public int maxHp;
    public int attack;
}

[CreateAssetMenu] を付けることで、 Unityエディタ上からこのデータを簡単に作成できるようになります。

Step2. データアセットを作成する

次に、実際のデータを作ります。

プロジェクトウィンドウを右クリックし、 「Create」→「Game Data」→「Enemy Data」を選択すると、 .asset ファイルが作成されます。

このアセットを選択すると、Inspectorに

  • 敵の名前
  • 最大HP
  • 攻撃力

といった項目が表示され、 コードを書かずに数値を編集できます。

Step3. MonoBehaviourからデータを参照する

次に、このデータを実際のロジック側で使います。

敵の挙動を制御するスクリプト(例:Enemy)を作成し、 ScriptableObjectを参照するフィールドを定義します。


using UnityEngine;

public class Enemy : MonoBehaviour
{
    [SerializeField]
    private EnemyData data;

    private int currentHp;

    private void Start()
    {
        currentHp = data.maxHp;
    }
}

Inspector上で、 作成した EnemyData アセットを ドラッグ&ドロップで割り当てるだけでOKです。

データを共有できることの強さ

この設計のポイントは、 複数の敵オブジェクトが同じEnemyDataを参照できることです。

例えば、

  • スライム(弱)
  • スライム(中)
  • スライム(強)

といった敵も、 データアセットを分けるだけで簡単に表現できます。

数値調整はアセット1か所だけ。 コードは一切触りません。

データが増えてきたときの悩み

ここまででも十分便利ですが、 データ項目が増えてくると、こんな悩みが出てきます。

  • Inspectorが縦に長くなって見づらい
  • 条件付きで表示を切り替えたい
  • 入力ミスを減らしたい

こうした「データ定義が増えた後」のストレスを減らしたい場合に、 役立つツールがあります。

Odin Inspector and Serializerは、 ScriptableObjectのInspector表示を大幅に改善できる拡張アセットです。

大量のデータを扱うプロジェクトや、 データ駆動設計を本格的に進めたい場合は、 検討する価値があります。

Odin Inspector and Serializer
✅ Unity Asset Storeでチェックする

次の章では、 ScriptableObjectをさらに活用し、 システム同士の依存を減らす「イベント設計」について解説します。




4. ScriptableObjectイベントで疎結合な設計にする

ScriptableObjectは、 データ管理だけでなく「イベントの受け渡し」にも活用できます。

これを使うことで、 「誰が誰を呼んでいるのか分からない」状態や、 Singletonだらけの設計から一歩抜け出せます。

なぜイベントで設計すると疎結合になるのか

例えば、プレイヤーが倒れたときに

  • ゲームオーバー画面を表示する
  • BGMを止める
  • リザルト処理を始める

これらをすべて直接呼び出していたら、 プレイヤーのスクリプトは大量の依存を抱えることになります。

イベント設計では、

「何が起きたか」だけを通知し、「何をするか」は受信側に任せる

という形に分けます。

GameEvent(発信側)を作る

まずは、イベントそのものを表すScriptableObjectを作成します。

プロジェクトウィンドウを右クリックし、 「Create」→「C# Script」を選んで、 名前を GameEvent にします。


using System.Collections.Generic;
using UnityEngine;

[CreateAssetMenu(menuName = "Game Event")]
public class GameEvent : ScriptableObject
{
    private List<GameEventListener> listeners = new();

    public void Raise()
    {
        for (int i = listeners.Count - 1; i >= 0; i--)
        {
            listeners[i].OnEventRaised();
        }
    }

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

    public void Unregister(GameEventListener listener)
    {
        listeners.Remove(listener);
    }
}

このScriptableObjectが、 「イベントの実体」になります。

GameEventListener(受信側)を作る

次に、イベントを受け取る側のコンポーネントを作ります。

新しくC#スクリプトを作成し、 名前を GameEventListener とします。


using UnityEngine;
using UnityEngine.Events;

public class GameEventListener : MonoBehaviour
{
    [SerializeField]
    private GameEvent gameEvent;

    [SerializeField]
    private UnityEvent response;

    private void OnEnable()
    {
        gameEvent.Register(this);
    }

    private void OnDisable()
    {
        gameEvent.Unregister(this);
    }

    public void OnEventRaised()
    {
        response.Invoke();
    }
}

Inspectorから、

  • どのGameEventを監視するか
  • イベントが来たら何をするか

を設定できるようになります。

イベントアセットを作ってつなぐ

次に、実際のイベントアセットを作成します。

プロジェクトウィンドウを右クリックし、 「Create」→「Game Event」を選んで、 例えば OnPlayerDied という名前を付けます。

受信したいGameObjectに GameEventListener をアタッチし、 Inspectorで

  • OnPlayerDied を割り当てる
  • 実行したいメソッドを UnityEvent に登録する

だけで連携が完成します。

イベントを発火する

あとは、発信側のロジックから


onPlayerDiedEvent.Raise();

と呼ぶだけです。

誰が受け取るかは知りません。 でも、必要な処理はすべて動きます。

この設計のメリット

  • クラス同士が直接依存しない
  • 機能の追加・削除が安全にできる
  • テストや差し替えがしやすい

ScriptableObjectイベントは、 「依存を増やさずに機能を増やしたい」ときにとても強力です。

次の章では、 データ駆動設計でよく悩みがちな 「実行時データと保存データの扱い」について整理します。




5. 実行時データと保存データをどう扱うか

データ駆動設計を進めていくと、 多くの人が一度はここで悩みます。

「ScriptableObjectの値って、実行中に変えていいの?」
「セーブデータはどう管理するのが正解?」

この章では、 ScriptableObjectを使った設計で破綻しやすいポイントを整理します。

ScriptableObjectは「定義データ」として扱う

まず大前提として、 ScriptableObjectは設計上「マスターデータ」として扱うのが安全です。

例えば、

  • 敵の最大HP
  • 攻撃力の基礎値
  • アイテムの性能

といった初期値・基準値を定義する役割です。

戦闘中に減っていくHPや、 一時的なバフ・デバフの値は、 MonoBehaviour側の変数で管理します。

実行中にScriptableObjectを変更するとどうなるか

Play中にScriptableObjectの値を直接変更すると、

  • エディタ停止後も値が残ることがある
  • 意図せずデータが書き換わってしまう

といったトラブルが起きがちです。

これを避けるために、よく使われる方法が

  • 実行時は Instantiate でコピーを作る
  • 変更される値は最初から別クラスで管理する

といったアプローチです。

セーブデータが絡むと一気に難しくなる

さらに悩ましいのが、 セーブ/ロードが必要になったときです。

データ駆動設計では、

  • ScriptableObject → 設計データ
  • セーブデータ → プレイ結果

という役割分担をすると、整理しやすくなります。

とはいえ、

  • 自前でシリアライズを書くのは大変
  • 対応漏れやバグが出やすい

というのも事実です。

保存処理を安全に任せたい場合

データ駆動設計を進めたあとに 「ここから先は作るより任せたい」と感じる人も多いと思います。

そんなときに選択肢になるのが、 Easy Save のようなセーブ専用アセットです。

ScriptableObjectやクラス構造を意識せず、 比較的シンプルにセーブ/ロードを実装できるため、

  • 設計に集中したい
  • 保存周りでハマりたくない

というケースでは、十分検討する価値があります。

Easy Save
✅ Unity Asset Storeでチェックする

次の章では、 ここまでの内容を踏まえつつ、 さらに一段上の設計を目指す考え方を紹介します。




6. さらに一段上の設計を目指す人へ(設計思考・ECS)

ここまでで、 ScriptableObjectを中心としたデータ駆動設計の基本形はひと通り押さえました。

この時点でも、

  • ハードコーディングは大幅に減り
  • 依存関係は整理され
  • 変更に強い構造

を作れるようになっているはずです。

ただ、プロジェクトがさらに大きくなったり、 「この設計で本当にいいのかな?」と考え始めたとき、 もう一段深い設計の視点が役立ちます。

データ駆動設計とECSの関係

Unityには、 ECS(Entity Component System)というアーキテクチャがあります。

ECSは、

  • データ(Component)
  • 処理(System)

を強く分離し、 継承よりもコンポジション(合成)を重視する設計です。

ScriptableObjectを使ったデータ駆動設計は、 このECS的な考え方へのとても良い入り口になります。

いきなりECSやDOTSに移行しなくても、

  • データと処理を分けて考える
  • 依存を減らす
  • 変更に強い構造を作る

この感覚を身につけておくだけで、 将来の選択肢が大きく広がります。

「設計の引き出し」を増やすという考え方

設計に正解はありません。

ただし、

「なぜこの設計にしているのか」を説明できるかどうか

は、とても大きな違いになります。

ScriptableObjectを使う理由、 イベントで疎結合にする理由、 データを分ける理由。

それらを言語化できるようになると、 設計は「なんとなく」から「選択」へ変わります。

設計そのものを学びたい人へ

Unityの具体的な書き方を超えて、 ゲーム設計そのものの考え方を学びたい場合、

Game Programming Patternsはとても良い一冊です。

ScriptableObjectによる設計や、 イベント駆動・疎結合の考え方も、 より深いレベルで理解できるようになります。

Game Programming Patterns
✅ Amazonでチェックする
✅ 楽天でチェックする




まとめ

この記事では、Unity開発で多くの人が一度はぶつかる 「ハードコーディングによる設計の限界」から抜け出すために、 データ駆動設計という考え方を紹介してきました。

最初は手軽に感じるMonoBehaviour直書きの実装も、 要素が増えるにつれて

  • 修正が怖くなる
  • 依存関係が絡まる
  • 調整のテンポが落ちる

といった問題を引き起こしがちです。

ScriptableObjectを使ったデータ駆動設計では、

  • データとロジックを分離できる
  • 変更に強い構造を作れる
  • チーム開発や後工程が楽になる

といったメリットを、無理なく得られます。

また、イベント設計を組み合わせることで、 Singletonに頼らない疎結合なシステムも実現できます。

いきなり完璧な設計を目指す必要はありません。

まずは、

  • よく使う数値をScriptableObjectに切り出す
  • 依存が増えそうなところをイベントにする

そんな小さな一歩でも、 プロジェクト全体の見通しは大きく変わります。

私自身も、 「もっと早くこの設計を知っていれば…」と感じた場面が何度もありました。

今の設計に少しでも不安があるなら、 ぜひデータ駆動設計を取り入れてみてください。


あわせて読みたい

データ駆動設計やScriptableObjectの理解をさらに深めたい方は、 以下の記事もあわせて読むのがおすすめです。

どの記事も、 「とりあえず動く」から「長く育てられる設計」へステップアップするための内容です。


参考文献

本記事は、上記の公式ドキュメント・技術解説・実践事例を参考にしつつ、 Unity開発の現場で使いやすい形に整理・再構成しています。


よくある質問(FAQ)

Q
ScriptableObjectは小規模なプロジェクトでも使うべきですか?
A

必ずしも最初から使う必要はありません。

ただし、 「同じ数値を複数の場所で使い回している」 「後から調整する可能性が高いデータがある」 と感じた時点で、ScriptableObjectに切り出す価値は十分あります。

小規模なプロジェクトでも、 あとから大きくなる可能性がある部分だけをデータ化しておくと、 設計のやり直しを避けやすくなります。

Q
ScriptableObjectとPrefabはどう使い分ければいいですか?
A

役割が少し違います。

Prefabは、

  • 見た目
  • コンポーネント構成
  • 初期状態

といったオブジェクトの構造を保存するものです。

一方、ScriptableObjectは、

  • 数値
  • 設定
  • ルール

といったロジックから独立したデータ定義に向いています。


PrefabからScriptableObjectを参照する形にすると、
「構造」と「内容」をきれいに分離できます。

Q
データ駆動設計にすると、逆に複雑になりませんか?
A

やり方次第では、複雑になります。

最初からすべてをデータ化しようとすると、

  • ファイルが増えすぎる
  • 追いかけるのが大変になる

といった状態になりがちです。

おすすめなのは、

  • 変更頻度が高いもの
  • 複数箇所で共有されるもの

から少しずつデータ駆動にすることです。

「今ハードコーディングで困っている部分」だけを切り出す。 それだけでも、設計はかなり楽になります。

※当サイトはアフィリエイト広告を利用しています。リンクを経由して商品を購入された場合、当サイトに報酬が発生することがあります。

※本記事に記載しているAmazon商品情報(価格、在庫状況、割引、配送条件など)は、執筆時点のAmazon.co.jp上の情報に基づいています。
最新の価格・在庫・配送条件などの詳細は、Amazonの商品ページをご確認ください。

スポンサーリンク