Unity DOTS / ECS 入門 — 大量エンティティを高速処理する

Unity の DOTS(Data-Oriented Technology Stack)を使うと、何万もの敵・パーティクル・弾丸を MonoBehaviour では不可能だった速度で処理できます。この記事では ECS・Job System・Burst コンパイラの基本と実践的な使い方を解説します。

DOTS とは

DOTS は Unity が提供するデータ指向プログラミングの技術スタックで、以下の 3 つから構成されます。

コンポーネント役割
ECS(Entity Component System)データ構造の設計思想。オブジェクトをデータ(Component)と処理(System)に分離
Job Systemマルチスレッド処理。メインスレッドから安全にワーカースレッドへ処理を委譲
Burst コンパイラC# コードをネイティブコード(SIMD 最適化あり)にコンパイルして高速化

これらを組み合わせることで、従来の MonoBehaviour アーキテクチャに比べて10〜100 倍のパフォーマンスが得られるケースがあります。

MonoBehaviour との根本的な違い

MonoBehaviour(OOP)

GameObject
├── Transform
├── MonoBehaviour(スクリプト)
│   ├── フィールド(データ)
│   └── Update()(処理)
└── Renderer

MonoBehaviour はデータと処理が一体化しており、メモリ上でバラバラに配置されます(キャッシュミスが発生しやすい)。

ECS(データ指向)

Entity(ID のみ)
  ↓ 参照
Component(純粋なデータ): Position, Velocity, Health...
  ↓ 処理
System(純粹な処理): MoveSystem, DamageSystem...

ECS では同じ種類の Component が連続したメモリ領域(Chunk)に配置されます。System が大量のデータを処理するとき、CPU のキャッシュに乗りやすく大幅に高速化されます。

パッケージのインストール

Unity 6 では Package Manager から以下をインストールします。

Window > Package Manager > Unity Registry で検索

- Entities(com.unity.entities)1.x
- Entities Graphics(com.unity.entities.graphics)
- Burst(com.unity.burst)
- Collections(com.unity.collections)
- Mathematics(com.unity.mathematics)

または Packages/manifest.json に直接追加します。

{
  "dependencies": {
    "com.unity.entities": "1.4.3",
    "com.unity.entities.graphics": "1.4.3",
    "com.unity.burst": "1.8.17",
    "com.unity.collections": "2.5.1",
    "com.unity.mathematics": "1.3.2"
  }
}

基本の実装

Component の定義

Component は IComponentData を実装した struct です。データのみを持ち、メソッドを書きません。

using Unity.Entities;
using Unity.Mathematics;

// 位置コンポーネント
public struct Position : IComponentData
{
    public float3 Value;
}

// 速度コンポーネント
public struct Velocity : IComponentData
{
    public float3 Value;
}

// 体力コンポーネント
public struct Health : IComponentData
{
    public float Current;
    public float Max;
}

Entity の生成

using Unity.Entities;
using Unity.Mathematics;

public class EnemySpawner : MonoBehaviour
{
    void Start()
    {
        // EntityManager を取得
        var world = World.DefaultGameObjectInjectionWorld;
        var entityManager = world.EntityManager;

        // アーキタイプ(Component の組み合わせ)を定義
        var archetype = entityManager.CreateArchetype(
            typeof(Position),
            typeof(Velocity),
            typeof(Health)
        );

        // 10000 体の敵を一括生成
        using var entities = entityManager.CreateEntity(archetype, 10000, Allocator.Temp);
        for (int i = 0; i < entities.Length; i++)
        {
            entityManager.SetComponentData(entities[i], new Position
            {
                Value = new float3(i * 0.1f, 0, 0)
            });
            entityManager.SetComponentData(entities[i], new Velocity
            {
                Value = new float3(0, 0, 1f)
            });
            entityManager.SetComponentData(entities[i], new Health
            {
                Current = 100f,
                Max = 100f
            });
        }
    }
}

System の定義

System は ISystem または SystemBase を継承します。Unity 6 では ISystem(struct ベース)が推奨です。

using Unity.Entities;
using Unity.Burst;
using Unity.Mathematics;

// Burst コンパイルを有効化
[BurstCompile]
public partial struct MoveSystem : ISystem
{
    [BurstCompile]
    public void OnCreate(ref SystemState state) { }

    [BurstCompile]
    public void OnUpdate(ref SystemState state)
    {
        float deltaTime = SystemAPI.Time.DeltaTime;

        // Position と Velocity を持つ全 Entity を処理
        foreach (var (position, velocity) in
            SystemAPI.Query<RefRW<Position>, RefRO<Velocity>>())
        {
            position.ValueRW.Value += velocity.ValueRO.Value * deltaTime;
        }
    }

    [BurstCompile]
    public void OnDestroy(ref SystemState state) { }
}

Job System との組み合わせ(より高速化)

IJobEntity を使うと処理をワーカースレッドに並列実行させられます。

using Unity.Entities;
using Unity.Burst;
using Unity.Mathematics;

[BurstCompile]
public partial struct MoveJob : IJobEntity
{
    public float DeltaTime;

    // Position(書き込み)と Velocity(読み取り)を持つ Entity に自動適用
    public void Execute(ref Position position, in Velocity velocity)
    {
        position.Value += velocity.Value * DeltaTime;
    }
}

[BurstCompile]
public partial struct MoveSystem : ISystem
{
    [BurstCompile]
    public void OnUpdate(ref SystemState state)
    {
        new MoveJob
        {
            DeltaTime = SystemAPI.Time.DeltaTime
        }.ScheduleParallel(); // 並列実行
    }
}

SubScene と Authoring(ECS と GameObject の橋渡し)

ECS の Entity はエディタ上で直接配置できないため、SubSceneAuthoring という仕組みを使います。

Authoring コンポーネント

using Unity.Entities;
using UnityEngine;

// エディタ上で設定するコンポーネント(MonoBehaviour)
public class EnemyAuthoring : MonoBehaviour
{
    public float Speed = 5f;
    public float MaxHealth = 100f;
}

// Baker: Authoring → ECS Component に変換
public class EnemyBaker : Baker<EnemyAuthoring>
{
    public override void Bake(EnemyAuthoring authoring)
    {
        var entity = GetEntity(TransformUsageFlags.Dynamic);
        AddComponent(entity, new Velocity { Value = new float3(0, 0, authoring.Speed) });
        AddComponent(entity, new Health { Current = authoring.MaxHealth, Max = authoring.MaxHealth });
    }
}

SubScene の使い方

  1. GameObject > New Sub Scene > Empty Scene
  2. SubScene 内に Authoring コンポーネントを持つ GameObject を配置
  3. ゲームを実行すると自動的に Entity に変換される

DOTS を使うべき場面・使わない場面

向いている場面

  • 大量の同種オブジェクト: 敵 1 万体・弾丸・パーティクル・草・木など
  • シミュレーション処理: 群衆 AI・交通シミュレーション・流体シミュレーション
  • 物理の大量計算: カスタム物理・コリジョン処理
  • RTS・塔防ゲームの大規模バトル

向いていない場面

  • 少数のユニーク GameObject: プレイヤー・ボス・カメラなど
  • Unity UI(Canvas): UI システムは ECS 非対応
  • 複雑なアニメーション: Animator とのフル統合はまだ限定的
  • プロトタイプ・ジャム: 開発速度重視なら MonoBehaviour が速い

MonoBehaviour との共存パターン

ECS と MonoBehaviour は共存できます。典型的な設計は以下の通りです。

MonoBehaviour 担当:
- プレイヤー制御
- UI 管理
- カメラ
- 演出・エフェクト管理

ECS 担当:
- 敵の大群処理
- 弾丸・パーティクル
- 地形データ
- シミュレーション計算

ハマりやすいポイント

[BurstCompile] を付け忘れると遅い

Burst コンパイルなしでも動きますが、マネージドコードのままになり速度が出ません。ISystemIJobEntity の両方に [BurstCompile] を付けてください。

// NG: Burst なし
public partial struct MoveSystem : ISystem
{
    public void OnUpdate(ref SystemState state) { ... }
}

// OK: Burst あり
[BurstCompile]
public partial struct MoveSystem : ISystem
{
    [BurstCompile]
    public void OnUpdate(ref SystemState state) { ... }
}

Burst 非対応の型を使うとコンパイルエラー

Burst は管理型(stringList<T>UnityEngine.Object など)を扱えません。

// NG: Burst Job 内で string は使えない
[BurstCompile]
public partial struct BadJob : IJobEntity
{
    public string Name; // コンパイルエラー
}

// OK: FixedString を使う
[BurstCompile]
public partial struct GoodJob : IJobEntity
{
    public FixedString64Bytes Name;
}

Allocator の選択ミス

NativeArray などのコレクションには Allocator を指定します。用途を間違えるとパフォーマンス低下やメモリリークが起きます。

Allocator用途
Allocator.Temp1 フレーム以内(最速)
Allocator.TempJob4 フレーム以内(Job 用)
Allocator.Persistent長期保持(最も遅い確保)
// 1 フレーム内で使い捨てる場合
using var array = new NativeArray<float>(1000, Allocator.Temp);

SystemBase と ISystem の違い

SystemBase(クラスベース)は使いやすいですが、Burst に完全対応しません。Unity 6 以降は ISystem(struct ベース)を使うのが推奨です。

// 旧来の書き方(部分的に Burst 非対応)
public partial class OldMoveSystem : SystemBase
{
    protected override void OnUpdate() { ... }
}

// 推奨(Unity 6 / Entities 1.x)
[BurstCompile]
public partial struct NewMoveSystem : ISystem
{
    [BurstCompile]
    public void OnUpdate(ref SystemState state) { ... }
}

まとめ

  • DOTS は ECS・Job System・Burst の組み合わせで大量オブジェクト処理を高速化する
  • ECS はデータ(Component)と処理(System)を分離し、メモリ効率を最大化する
  • IJobEntity + [BurstCompile] で並列・SIMD 最適化された処理を書ける
  • SubScene と Authoring/Baker で Unity エディタとの橋渡しを行う
  • MonoBehaviour の代替ではなく補完的な関係。大量処理が必要な部分に絞って使うのが現実的