mcpField
U
unity-data-driven

unity-data-driven

github.com/nice-wolf-studio開発4073 回インストール6 週間前に更新
リポジトリ

unity-data-driven について

unity-data-driven は、Unity のゲーム開発者が設定データの保存、構成、移行方法を選ぶための判断ガイドです。ScriptableObject、JSON/CSV のパイプライン、デザイナーの編集フロー、シリアライズ上の注意点を扱います。

主な機能

  1. 01設定データの保存方法を選択
  2. 02継承とコンポジションを比較
  3. 03JSON データパイプラインを設計
  4. 04デザイナーの編集フローを設計
  5. 05データのバージョン管理と移行

活用シーン

  1. 01敵のステータスやレベルを設定
  2. 02表計算データからゲーム設定を取り込む
  3. 03一括編集ツールを選択
  4. 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 のアーキテクチャに関する判断

追加リソース

コメント