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 はエディタ上で直接配置できないため、SubScene と Authoring という仕組みを使います。
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 の使い方
GameObject > New Sub Scene > Empty Scene- SubScene 内に Authoring コンポーネントを持つ GameObject を配置
- ゲームを実行すると自動的に Entity に変換される
DOTS を使うべき場面・使わない場面
向いている場面
- 大量の同種オブジェクト: 敵 1 万体・弾丸・パーティクル・草・木など
- シミュレーション処理: 群衆 AI・交通シミュレーション・流体シミュレーション
- 物理の大量計算: カスタム物理・コリジョン処理
- RTS・塔防ゲームの大規模バトル
向いていない場面
- 少数のユニーク GameObject: プレイヤー・ボス・カメラなど
- Unity UI(Canvas): UI システムは ECS 非対応
- 複雑なアニメーション: Animator とのフル統合はまだ限定的
- プロトタイプ・ジャム: 開発速度重視なら MonoBehaviour が速い
MonoBehaviour との共存パターン
ECS と MonoBehaviour は共存できます。典型的な設計は以下の通りです。
MonoBehaviour 担当:
- プレイヤー制御
- UI 管理
- カメラ
- 演出・エフェクト管理
ECS 担当:
- 敵の大群処理
- 弾丸・パーティクル
- 地形データ
- シミュレーション計算
ハマりやすいポイント
[BurstCompile] を付け忘れると遅い
Burst コンパイルなしでも動きますが、マネージドコードのままになり速度が出ません。ISystem と IJobEntity の両方に [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 は管理型(string・List<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.Temp | 1 フレーム以内(最速) |
Allocator.TempJob | 4 フレーム以内(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 の代替ではなく補完的な関係。大量処理が必要な部分に絞って使うのが現実的