Unityでゲームを作っていると、最初はシンプルだったはずのコードが、気づけば修正のたびに不安になる状態になっていませんか?
敵のHPを少し変えたいだけなのにスクリプトを開いて数値を書き換え、影響範囲が怖くてテストに時間がかかる。 Singletonが増えすぎて「これ、どこから呼ばれてるんだっけ?」と頭を抱える……。 実はこれ、Unity開発でとてもよくある“成長痛”なんです。
その原因の多くは、ロジックとデータが強く結びついたまま(ハードコーディングされたまま)設計が進んでしまうことにあります。
この記事では、そうした悩みを解決する考え方として「データ駆動設計」を取り上げ、 Unityで実践しやすい形に落とし込んで解説していきます。
中心となるのは ScriptableObject。 「名前は知っているけど、正直Prefabと何が違うの?」 「使ってみたけど、設計として合っているのか自信がない」 そんな状態から一歩抜け出すことを目標にします。
この記事を読み終える頃には、
- なぜハードコーディングが問題になりやすいのか
- ScriptableObjectを使うと何がどう改善されるのか
- 依存を増やさずに拡張していく設計の考え方
これらが、感覚ではなく言葉と構造で説明できるようになります。
小規模なプロジェクトでも、中〜大規模を見据えた開発でも役立つ内容なので、 「今の設計、ちょっと不安かも…」と感じているなら、ぜひこのまま読み進めてみてくださいね 🙂
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の設計パターンを徹底比較!シングルトン・サービスロケーター・イベント駆動の違い
- Unityのデータ管理を最適化!SerializableとScriptableObjectの違いと使い分け
- 【実践】UnityでJSONデータ管理!セーブ&ロード設計の基本
- Unityで依存を減らす!依存性注入(DI)を使ったクリーンな設計入門
どの記事も、 「とりあえず動く」から「長く育てられる設計」へステップアップするための内容です。
参考文献
- Unity公式:ScriptableObjectを使ったモジュラーなゲームアーキテクチャ
- Unity Manual:Best Practice Guides(設計・開発の公式指針)
- ScriptableObjectの基礎と使いどころまとめ
- UnityにおけるScriptableObjectとデータ駆動設計
- ScriptableObjectベースのゲームアーキテクチャ解説
- Game Dev Beginner:Scriptable Objects in Unity
- YouTube:ScriptableObjectを使ったデータ駆動設計の解説動画
- YouTube:ScriptableObjectとイベント設計の実践例
- GameDev StackExchange:Unityにおけるデータ駆動UI設計の考え方
- Wikipedia:Entity Component System(ECS)
本記事は、上記の公式ドキュメント・技術解説・実践事例を参考にしつつ、 Unity開発の現場で使いやすい形に整理・再構成しています。
よくある質問(FAQ)
- QScriptableObjectは小規模なプロジェクトでも使うべきですか?
- A
必ずしも最初から使う必要はありません。
ただし、 「同じ数値を複数の場所で使い回している」 「後から調整する可能性が高いデータがある」 と感じた時点で、ScriptableObjectに切り出す価値は十分あります。
小規模なプロジェクトでも、 あとから大きくなる可能性がある部分だけをデータ化しておくと、 設計のやり直しを避けやすくなります。
- QScriptableObjectとPrefabはどう使い分ければいいですか?
- A
役割が少し違います。
Prefabは、
- 見た目
- コンポーネント構成
- 初期状態
といったオブジェクトの構造を保存するものです。
一方、ScriptableObjectは、
- 数値
- 設定
- ルール
といったロジックから独立したデータ定義に向いています。
PrefabからScriptableObjectを参照する形にすると、
「構造」と「内容」をきれいに分離できます。
- Qデータ駆動設計にすると、逆に複雑になりませんか?
- A
やり方次第では、複雑になります。
最初からすべてをデータ化しようとすると、
- ファイルが増えすぎる
- 追いかけるのが大変になる
といった状態になりがちです。
おすすめなのは、
- 変更頻度が高いもの
- 複数箇所で共有されるもの
から少しずつデータ駆動にすることです。
「今ハードコーディングで困っている部分」だけを切り出す。 それだけでも、設計はかなり楽になります。







※当サイトはアフィリエイト広告を利用しています。リンクを経由して商品を購入された場合、当サイトに報酬が発生することがあります。
※本記事に記載しているAmazon商品情報(価格、在庫状況、割引、配送条件など)は、執筆時点のAmazon.co.jp上の情報に基づいています。
最新の価格・在庫・配送条件などの詳細は、Amazonの商品ページをご確認ください。