unity-data-driven について
unity-data-driven は、Unity のゲーム開発者が設定データの保存、構成、移行方法を選ぶための判断ガイドです。ScriptableObject、JSON/CSV のパイプライン、デザイナーの編集フロー、シリアライズ上の注意点を扱います。
主な機能
- 01設定データの保存方法を選択
- 02継承とコンポジションを比較
- 03JSON データパイプラインを設計
- 04デザイナーの編集フローを設計
- 05データのバージョン管理と移行
活用シーン
- 01敵のステータスやレベルを設定
- 02表計算データからゲーム設定を取り込む
- 03一括編集ツールを選択
- 04既存データ形式を更新
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ nice-wolf-studio/ unity-claude-skills --skill unity-data-drivenインストール後、Claude Code で/unity-data-drivenを入力して読み込まれたか確認します。
同梱ファイルreferences/data-pipeline-patterns.md
詳細
データ駆動型設計 -- 判断パターン
前提スキル:
unity-scripting/references/scriptableobjects.md(SO の作成、イベントチャネル、ランタイムセット、変数参照)、unity-editor-tools(カスタムインスペクター、EditorWindow)
これらのパターンでは、最もよくあるデータ管理上の失敗に対処します。Claude が MonoBehaviour に調整可能な値をハードコードしたり、マジックナンバーを使用したりすることで、反復作業やバランス調整が困難になります。ゲームデータはすべて、コードを変更せずにデザイナーが編集できるようにする必要があります。
パターン:設定データの保存先の選択
WHEN: ゲーム設定(敵のステータス、レベルのレイアウト、戦利品テーブルなど)の保存先を選ぶ場合
DECISION:
- ScriptableObject アセット(デフォルト) -- インスペクターでデザイナーが編集でき、型安全で、リファクタリングが可能で、YAML としてバージョン管理できます。ほとんどのゲーム設定に最適です。
- JSON/CSV ファイル -- 外部ツール(スプレッドシート、Web ツール)でデータを生成する場合、MOD のサポートが必要な場合、または一括編集が必要な場合に使用します。ランタイムに読み込みます。
- 埋め込み定数 -- 物理定数、数学、プロトコルのバージョンなど、本当に固定された値に使用します。
static readonlyまたはconstを使います。
SCAFFOLD(ScriptableObject 設定):
[CreateAssetMenu(fileName = "New Enemy Config", menuName = "Game/Enemy Config")]
public class EnemyConfig : ScriptableObject
{
[Header("Stats")]
[Min(1)] public int maxHealth = 100;
[Range(0f, 20f)] public float moveSpeed = 5f;
[Range(0f, 50f)] public float attackDamage = 10f;
[Header("AI")]
[Range(1f, 50f)] public float detectionRange = 15f;
[Range(0.5f, 5f)] public float attackCooldown = 1.5f;
[Header("Rewards")]
[Min(0)] public int xpReward = 25;
[Tooltip("Loot dropped on death. Leave empty for no loot.")]
public LootTable lootTable;
}
// Usage in MonoBehaviour:
public class Enemy : MonoBehaviour
{
[SerializeField] private EnemyConfig config; // Assign in Inspector
void Awake()
{
// Read from config -- never hardcode values
_health = new HealthSystem(config.maxHealth);
_moveSpeed = config.moveSpeed;
}
}
GOTCHA: エディターでランタイムに変更した ScriptableObject アセットは、その変更がディスクに保存されます(.asset ファイルが保存されます)。ビルドでは、アプリケーションの終了時にランタイムの変更は失われます。セーブデータにランタイムの SO 変更を決して利用しないでください。別のセーブシステムを使用します。永続化については unity-save-system を参照してください。
パターン:ScriptableObject の継承とコンポジション
WHEN: 複数の設定タイプで共通の基本フィールドを共有する場合(すべての敵が HP/速度を持ち、サブタイプごとに飛行型/遠距離型/ボス用フィールドを追加するなど)
DECISION:
- SO の継承 --
EnemyConfig : ScriptableObject、FlyingEnemyConfig : EnemyConfigのようにします。各サブタイプで独自のフィールドを追加します。デザイナーには、各アセットに関連するフィールドのみが表示されます。階層が 2~3 段階の場合に適しています。 - ネストした
[Serializable]構造体(コンポジション) --EnemyConfigに[SerializeField] MovementConfig movement; [SerializeField] AttackConfig attack;を含めます。組み合わせて使用できます。多様性の幅が広い場合、継承よりもすっきりすることがよくあります。
SCAFFOLD(コンポジション):
[System.Serializable]
public struct MovementConfig
{
[Range(0f, 20f)] public float speed;
public bool canFly;
[Tooltip("Only used if canFly is true")]
[Range(0f, 50f)] public float flyHeight;
}
[System.Serializable]
public struct AttackConfig
{
public AttackType type;
[Range(0f, 100f)] public float damage;
[Range(0.1f, 10f)] public float cooldown;
[Range(0f, 30f)] public float range;
}
[CreateAssetMenu(fileName = "New Enemy", menuName = "Game/Enemy Config")]
public class EnemyConfig : ScriptableObject
{
[Header("Identity")]
public string displayName;
[TextArea(2, 4)] public string description;
[Header("Movement")]
public MovementConfig movement;
[Header("Combat")]
public AttackConfig attack;
[Header("Stats")]
[Min(1)] public int maxHealth = 100;
[Min(0)] public int xpReward = 25;
}
SCAFFOLD(継承):
public abstract class EnemyConfig : ScriptableObject
{
[Min(1)] public int maxHealth = 100;
[Range(0f, 20f)] public float moveSpeed = 5f;
}
[CreateAssetMenu(fileName = "New Melee Enemy", menuName = "Game/Enemies/Melee")]
public class MeleeEnemyConfig : EnemyConfig
{
[Range(0f, 50f)] public float meleeDamage = 15f;
[Range(0.5f, 3f)] public float attackRadius = 1.5f;
}
[CreateAssetMenu(fileName = "New Ranged Enemy", menuName = "Game/Enemies/Ranged")]
public class RangedEnemyConfig : EnemyConfig
{
[Range(0f, 50f)] public float projectileDamage = 10f;
[Range(5f, 30f)] public float fireRange = 15f;
public GameObject projectilePrefab;
}
GOTCHA: SO の継承では、インスペクターにすべての基本フィールドが表示されます(非表示にはなりません)。基本フィールドがインスペクターを煩雑にする場合は、[HideInInspector] またはカスタムエディターを使用してください。[Serializable] 構造体を使ったコンポジションは、各構造体を無関係な設定タイプ間でも再利用できるため、よりすっきりすることがよくあります。[CreateAssetMenu] は具象(abstract ではない)ScriptableObject サブクラスでのみ機能します。
パターン:JSON データパイプライン
WHEN: 外部ソース(スプレッドシート、Web API、MOD など)からゲームデータを読み込む場合
DECISION:
- ビルド時パイプライン -- エディタースクリプトで JSON/CSV を ScriptableObject アセットに変換します。データはビルド時に組み込まれます。デザイナーがスプレッドシートを編集してインポートを実行すると、データが SO アセットになります。
- ランタイム読み込み -- ランタイムに StreamingAssets またはリモート URL から JSON を読み込みます。MOD、ホットリロード、サーバー主導の設定をサポートします。
SCAFFOLD(ランタイムの JSON 読み込み):
// Data class (plain C#, not ScriptableObject)
[System.Serializable]
public class WaveData
{
public int waveNumber;
public string[] enemyTypes;
public int[] enemyCounts;
public float spawnInterval;
}
[System.Serializable]
public class WaveDataList
{
public WaveData[] waves; // JsonUtility requires a wrapper for arrays
}
// Loading
public class WaveLoader : MonoBehaviour
{
async Awaitable<WaveData[]> LoadWaves()
{
string path = Path.Combine(Application.streamingAssetsPath, "waves.json");
#if UNITY_ANDROID && !UNITY_EDITOR
// Android: StreamingAssets is inside APK, must use UnityWebRequest
using var request = UnityWebRequest.Get(path);
await request.SendWebRequest();
string json = request.downloadHandler.text;
#else
string json = await File.ReadAllTextAsync(path);
#endif
var wrapper = JsonUtility.FromJson<WaveDataList>(json);
return wrapper.waves;
}
}
GOTCHA: JsonUtility は、辞書、トップレベルの配列(ラッパークラスが必要)、ポリモーフィック型、null 値(代わりにデフォルト値を使用)、プロパティ(フィールドのみ対応)をシリアライズできません。これらを扱う場合は、Newtonsoft JSON(com.unity.nuget.newtonsoft-json パッケージ)を使用してください。Application.streamingAssetsPath はすべてのプラットフォームで読み取り専用であり、Android では UnityWebRequest が必要です。エディターのインポートスクリプトについては references/data-pipeline-patterns.md を参照してください。
パターン:デザイナーへの引き継ぎワークフロー
使用場面:デザイナーがコードに触れずにゲームデータを作成・編集する必要がある場合
判断:
- SO + CreateAssetMenu -- デザイナーは Project ウィンドウで右クリックして設定アセットを作成し、Inspector で編集します。小〜中規模のデータセット(100 項目未満)に最適です。
- SO + Custom EditorWindow -- 一括編集、検証、検索/フィルターに適しています。大規模なデータセット(100 項目以上、戦利品テーブル、会話)では、導入する価値があります。
- 外部スプレッドシート + インポート -- Google Sheets/Excel → CSV → Editor インポートスクリプト。デザイナーがスプレッドシートを好む場合や、外部ツールからデータを取り込む場合に最適です。
スキャフォールド(自己説明型 Inspector):
[CreateAssetMenu(fileName = "New Weapon", menuName = "Game/Weapons/Weapon Config")]
public class WeaponConfig : ScriptableObject
{
[Header("Identity")]
[Tooltip("Display name shown in UI and inventory")]
public string displayName;
[TextArea(2, 5)]
[Tooltip("Flavor text shown in the item tooltip")]
public string description;
public Sprite icon;
[Header("Combat Stats")]
[Tooltip("Base damage before modifiers. Actual damage = base * level multiplier")]
[Range(1f, 200f)] public float baseDamage = 10f;
[Tooltip("Seconds between attacks")]
[Range(0.1f, 5f)] public float attackSpeed = 1f;
[Tooltip("Maximum range in world units")]
[Range(0.5f, 30f)] public float range = 2f;
[Header("VFX/SFX")]
[Tooltip("Played on hit. Leave null for no effect")]
public GameObject hitEffect;
[Tooltip("Played on attack")]
public AudioClip attackSound;
[Space(10)]
[Header("Advanced")]
[Tooltip("Damage falloff over distance. X = normalized distance (0-1), Y = damage multiplier")]
public AnimationCurve damageFalloff = AnimationCurve.Linear(0, 1, 1, 0.5f);
}
注意点:デザイナーはコードコメントを読めないため、すべてのフィールドに [Tooltip("...")] を使用してください。無効なデータの入力を防ぐには [Range(min, max)] を使用します。関連するフィールドをグループ化するには [Header("Section")] を使用します。視覚的に間隔を空けるには [Space(10)] を使用します。AnimationCurve フィールドは、デザイナーが調整できる減衰、イージング、応答曲線に便利です。省略可能な参照には、「効果なしにする場合は null のままにしてください」のようなツールチップを付けます。
パターン:データのバージョン管理と移行
使用場面:アップデート間でリリース済みデータ形式が変更される場合(フィールドの追加/名前変更/削除)
判断:
- SO の堅牢なシリアル化(追加的な変更) -- 新しいフィールドを追加すると、デフォルト値が設定されます。フィールドを削除すると、データは通知なく破棄されます。互換性を損なわない変更であれば安全です。Unity が自動的に処理します。
- 明示的なバージョンフィールド + マイグレーター -- 互換性を損なう変更(フィールド名の変更、データ構造の変更)に使用します。データに
int versionを保存し、読み込み時に移行を実行します。
スキャフォールド(名前変更時の FormerlySerializedAs):
using UnityEngine.Serialization;
public class EnemyConfig : ScriptableObject
{
// Renamed from "hp" to "maxHealth" -- existing assets preserved
[FormerlySerializedAs("hp")]
[Min(1)] public int maxHealth = 100;
// Renamed from "speed" to "moveSpeed"
[FormerlySerializedAs("speed")]
[Range(0f, 20f)] public float moveSpeed = 5f;
// New field -- existing assets get the default value (25)
[Min(0)] public int xpReward = 25;
// Removed field: just delete it. Existing assets silently drop the old data.
}
スキャフォールド(移行機能付きバージョン管理ランタイムデータ):
[System.Serializable]
public class PlayerData
{
public int version = 2; // Current schema version
public string playerName;
public int level;
public float[] position; // v2: changed from Vector3 to float[] for JSON compat
// Migration from v1 to v2
public static PlayerData MigrateFromV1(string json)
{
// v1 had "pos_x", "pos_y", "pos_z" as separate fields
var jObj = Newtonsoft.Json.Linq.JObject.Parse(json);
if ((int)jObj["version"] == 1)
{
float x = (float)jObj["pos_x"];
float y = (float)jObj["pos_y"];
float z = (float)jObj["pos_z"];
jObj.Remove("pos_x"); jObj.Remove("pos_y"); jObj.Remove("pos_z");
jObj["position"] = new Newtonsoft.Json.Linq.JArray(x, y, z);
jObj["version"] = 2;
}
return jObj.ToObject<PlayerData>();
}
}
注意点:[FormerlySerializedAs] は Unity のシリアル化(Inspector、.asset ファイル、プレハブ)でのみ機能します。JSON やカスタムシリアル化では機能しません。JSON を移行するには、新しい型にデシリアライズする前に生の JSON を解析する必要があります。SO データのバージョン管理とセーブデータのバージョン管理は別の問題です。セーブファイルの移行については unity-save-system を参照してください。
Inspector 属性クイックリファレンス
| 属性 | 用途 | 例 |
|---|---|---|
[Header("Section")] | セクションラベル | 関連するフィールドをグループ化 |
[Tooltip("Help text")] | ホバー時のヘルプテキスト | 分かりにくいフィールドを説明 |
[Range(min, max)] | 数値フィールドのスライダー | [Range(0, 100)] |
[Min(value)] | 最小値の制限 | 0 未満を許可しない場合は [Min(0)] |
[TextArea(min, max)] | 複数行のテキストフィールド | [TextArea(2, 5)] |
[Space(pixels)] | 垂直方向の間隔 | [Space(10)] |
[HideInInspector] | Inspector で非表示 | 内部フィールド |
[FormerlySerializedAs] | 名前変更時にデータを保持 | 移行の安全性 |
[ColorUsage(alpha, hdr)] | カラーピッカーの設定 | [ColorUsage(true, true)] |
[GradientUsage(hdr)] | グラデーションの HDR サポート | VFX のカラーカーブ |
[Delayed] | Enter キー押下時/フォーカス喪失時に適用 | キー入力ごとの更新を防止 |
[SerializeReference] | ポリモーフィックなシリアル化 | インターフェースフィールド |
関連スキル
- unity-scripting/references/scriptableobjects.md -- SO の作成、イベントチャネル、ランタイムセット、変数参照
- unity-editor-tools -- 一括編集用の Custom EditorWindow、カスタム表示用の PropertyDrawer
- unity-save-system -- ランタイムデータの永続化(設定データとの比較)
- unity-game-architecture -- ScriptableObject のアーキテクチャに関する判断