データガイドライン


目的

このページでは、ReactiveEntitySetに含めるべきデータとGameObjectに残すべきデータを説明します。これらのガイドラインに従うことで、よりクリーンなアーキテクチャとバグの減少につながります。


基本ルール

GameObjectの外部で計算または管理されるデータのみを含める。

このルールが、データがRESに属するかMonoBehaviourに属するかを決定します。


データ所有の例

データ 計算元 RESに含める? 理由
Health ダメージシステム(外部) はい 外部ロジックが変更
MaxHealth 設定/初期化 はい 参照データ
SpeedMultiplier バフシステム(外部) はい 外部ロジックが変更
IsStunned ステータスエフェクトシステム はい 外部ロジックが制御
KilledByPlayer デスシステム はい 外部フラグ
Position Transform(GameObject) いいえ GameObjectが所有
Rotation Transform(GameObject) いいえ GameObjectが所有
Scale Transform(GameObject) いいえ GameObjectが所有

Positionを除外する理由

Positionは重要なエンティティデータのように見えますが、通常はRESに含めるべきではありません。

理由

  • GameObjectのTransformコンポーネントがpositionを所有しており、所有権が競合します
  • Positionは毎フレーム変更されることが多く、RESのイベント通知コストが高くなります
  • RESに格納すると二重の真実源が生まれ、同期問題が発生します
  • Transformは既にpositionを公開しているため、RESに格納するメリットがありません

例外

サーバーがpositionを計算するネットワークゲームは異なります。

// サーバー権威の移動
// サーバーがpositionを計算し、クライアントに送信
// RESが権威的なソースになる

[Serializable]
public struct NetworkEntityState
{
    public Vector3 Position;      // サーバー計算、RESにOK
    public Quaternion Rotation;   // サーバー計算、RESにOK
    public int Health;
}

この場合、GameObjectのTransformはRES内の権威的なpositionのビューです。


設計チェックリスト

RESにデータを追加する前に、これらの質問をしてください。

graph TB
    A["このデータを<br/>RESに入れるべき?"] --> B{"外部システムが<br/>計算する?"}
    B -->|NO| C["GameObjectの<br/>フィールドとして維持"]
    B -->|YES| D{"変更頻度は<br/>10%未満/フレーム?"}
    D -->|NO| E["従来の<br/>アプローチを検討"]
    D -->|YES| F{"複数システムが<br/>変更を監視?"}
    F -->|NO| G["RESは<br/>不要かも"]
    F -->|YES| H["RESに追加"]

    style C fill:#ffcccc
    style E fill:#ffffcc
    style G fill:#ffffcc
    style H fill:#ccffcc

1. 所有権

このデータは外部ロジックによって計算されますか?

  • はい → RESを検討
  • いいえ(GameObjectが所有) → GameObjectフィールドとして維持してください

2. 変更頻度

フレームあたりエンティティの10%未満が変更されますか?

  • はい → RESは効率的です
  • いいえ → 従来のアプローチの方が良いかもしれません

3. 観測可能性

複数のシステムが変更に反応しますか?

  • はい → RESは組み込みイベントを提供します
  • いいえ → RESは不要かもしれません

4. シーン非依存

このデータはシーンロードをまたいで永続化しますか?

  • はい → RESはこれを自動的に提供します
  • いいえ → どちらのアプローチでも構いません

データ/ロジック分離

RESは自然にクリーンなアーキテクチャパターンを強制します。

パターン

State(struct)      : データのみ、メソッドなし
計算ロジック         : 別クラスの純粋関数
RES                  : ストレージと通知
GameObject           : 可視化とリアクション

この分離が有効な理由

State構造体は公開フィールドを持ちますが、ビジネスロジックはありません。

[Serializable]
public struct EnemyState
{
    public int Health;
    public int MaxHealth;
    public bool IsStunned;

    // プロパティはOK
    public float HealthPercent => MaxHealth > 0 ? (float)Health / MaxHealth : 0f;

    // ただし状態を変更するメソッドは不可
    // void TakeDamage(int damage) ← これはダメ
}

計算ロジックは別クラスに存在します。

public static class DamageCalculator
{
    public static EnemyState ApplyDamage(EnemyState state, int damage, bool isCritical)
    {
        int finalDamage = isCritical ? damage * 2 : damage;
        state.Health = Mathf.Max(0, state.Health - finalDamage);
        return state;
    }
}

使用方法でそれらを結びつけます。

enemySet.UpdateData(enemyId, state =>
    DamageCalculator.ApplyDamage(state, damage, isCritical));

メリット

メリット 説明
テスト可能性 純粋関数はユニットテストが簡単
再利用性 同じロジックがどのエンティティでも動作
明確さ 関心事の明確な分離
スレッドセーフティ 純粋関数は副作用がない

他のパラダイムとの対応

このデータ/ロジック分離は、現代のアーキテクチャパターンと一致しています。

パラダイム データ ロジック
関数型 イミュータブルデータ 純粋関数
ECS Component System
Redux State Reducer
RES State struct 計算クラス

これらのパターンに馴染みがあれば、RESアプローチは自然に感じられるでしょう。


非同期処理の可能性

データ/ロジック分離は将来の非同期処理を可能にします。

  • State(struct)はコピー可能で、スレッド間で渡せます
  • 計算ロジックは純粋関数であり、スレッドセーフです
  • RES更新はメインスレッドでの単一同期ポイントになります

重い計算はメインスレッド外で実行でき、完了時に結果をRESに適用できます。


よくある間違い

間違い1 – Transformデータの格納

// これは通常やってはいけない
[Serializable]
public struct EntityState
{
    public Vector3 Position;    // Transformが所有
    public Quaternion Rotation; // Transformが所有
    public int Health;
}

間違い2 – state structにロジックを入れる

// これはやってはいけない
[Serializable]
public struct EntityState
{
    public int Health;

    public void TakeDamage(int damage)  // ロジックはここに属さない
    {
        Health -= damage;
    }
}

間違い3 – 頻繁に変更されるデータを含める

// これが毎フレーム変更される場合は再検討
[Serializable]
public struct EntityState
{
    public float AnimationTime;  // 毎フレーム変更
    public Vector3 Velocity;     // 毎フレーム変更
}

まとめ

ガイドライン 説明
外部所有権 外部システムが計算するデータのみRESに
Transformデータなし Position/RotationはGameObjectに属する(通常)
低変更頻度 フレームあたりエンティティの10%未満
データ/ロジック分離 State構造体 + 純粋関数の計算クラス
ネットワークの例外 サーバー権威のデータはpositionを含められる

次のステップ


This site uses Just the Docs, a documentation theme for Jekyll.