Entity Component System




# はじめに

Unity関係で最近質問されたので, まとめておきます. もうUnity3Dとは言いません, CanonicalはUnityやめるってよ, 3年前だけど.

# 概要と歴史
Entity Component System (ECS)は, "継承より合成"の原則をゲームシステムにアレンジしたものです. "継承より合成"はGoF本のはじめにもあるように, 90年代に入る前に知られていたようです. はっきりとゲーム業界に持ち込まれたのは, 90年代終わりになります ["A Data-DrivenGame Object System"(GDC2002)(PDF注意)](https://www.gamedevs.org/uploads/data-driven-game-object-system.pdf).
ECSの前がどうかというと, "Intrinsic Alchemy"といった継承をベースとしたゲームシステムがありました. ゲーム規模の拡大にともない, 継承をベースにしたシステムでは, オブジェクト (キャラクタやエネミー) の大量なバリエーションを作成することが困難になり, ECSに進むことになります.

アレンジ部分については簡単なので, ここで述べてしまいます. ECSでは, 合成ではなく集約になります, "継承より集約"です. "合成"と"集約"の違いはいくつかありますが, ゲームシステムではオブジェクトの寿命の違いが重要になります. ゲームシステムでは, 集約元と集約先の寿命が一致しないことが都合のいいことがあります.

# 継承によるオブジェクトシステム
継承によるオブジェクトシステムは, 拡張性に乏しいものです. また, ソフトウェアの規模が拡大し, 分業が進むとプログラムだけで規模を拡大していくことが困難になります.
思いつく欠点は次のような感じでしょうか. GoF本や"Effective Java"にも書かれていることなので今更でしょう.

- 継承では拡張性に限界がある
	- 継承は結合度が強いため, 修正・変更が困難
		- あるオブジェクトを変更するために,  継承木のルートまでの経路上のオブジェクトと, 自分より子のオブジェクト全ての実装を把握する必要がある
		- 多重継承やMixinでは別の問題が生じる
		- インターフェイスは実装は共有できない
		- 同じ機能を新しいオブジェクトに使いたい場合, オブジェクトを継承木に組み込むしかない
- Data Driven (Data Oriented)が難しい

Doomは1993年, Quakeは1996年だったりするので, ゲーム業界が遅れて導入するのもわかります. Doomとか全部グローバル変数ですし.

Wikipediaの記事([Composition over inheritance](https://en.wikipedia.org/wiki/Composition_over_inheritance))の例が, ひと目で心を折ってくるためお薦めです.

# Entity Component System

[ES Terminology](http://entity-systems.wikidot.com/es-terminology)によると, ECSの定義は次のようになります.

1. Entity: ゲームオブジェクトを識別する一意のIDを保持するオブジェクト, 大抵はただの整数.
2. Component: オブジェクトの一つの性質を実装するオブジェクト. 振る舞いやデータを保持する.
3. System: オブジェクトの一つの性質を処理するコード. 一つの性質に対応するオブジェクトがある場合もある

"3." は理解が難しいかもしれませんが, "なんとかManager" のようなものです.
"Audio Source"Componentがあれば, "Audio System" が音声を再生する, ということです.

Unityは明らかにECSですが, 開発者はそうは思っていないようです [DOTS について：Entity Component System](https://blogs.unity3d.com/jp/2019/03/08/on-dots-entity-component-system/). 斜め読みですが, Entityが重すぎという理由らしいです, それにしてもECSというネーミングはナンセンスかと. はじめはGameObjectにtransformプロパティがあったり, 明らかに過剰でしたから, transformプロパティは`GetComponent<Transform>` の糖衣ですしね.

## 派生, バリエーション

設定ファイルからオブジェクトを構築する方法, オブジェクト間のメッセージング機構を追加したもの, "Structure of Arrays" の構造をとることで, とくに物理演算の高速化を狙ったものなど, あります.
"Autodesk Stingray"の前身, "BitSquid"の開発ブログはいい資料です,  [Welcome to the BitSquid blog](http://bitsquid.blogspot.com/2009/09/welcome-to-bitsquid-blog.html).

# 追記：設計

定義どおりにEntityは整数のIDだけを持つとすると, 次のような設計を思いつきます. 名前はいい加減です.

![ECS00.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/27065/fafb6d95-24ac-d93f-9d2f-08f62aa3ce0b.jpeg)

オブジェクトの持つデータは, システム側にあることが特徴です.

この設計の利点は, 各Componentのデータがメモリ上で近い位置にあることです. 各Componentのデータには同じ処理を行う可能性が高いため, 物理シミュレーションなどでは, SoAとの併用で高速化できる可能性があります. 削除と追加の繰り返しが起きた後も, 空きができないような構造やアルゴリズムを考える必要はあります.
この恩恵も, システム側が用意しているコンポーネントに対してだけです. システムとユーザプログラムが同じプログラミング言語なら, ユーザ側が独自に作ればよいだけなのですが, Unityはそうではないため"ECS"や"DOTS"を必要としたのかもしれません.

欠点は, ひとつはシステム側の実装が複雑なことです. さらに, `GetComponent<T>`を実現するとき, "entity id -> component id -> コンポーネントの実体"といった変換が入り効率的ではありません.
Unityの実装は知りませんし, ソースコードを見たとしても公表していいものではないですが, `GetComponent<T>`が遅い理由かもしれません.

## ID
IDはポインタではなく整数ですが, 大抵の実装では付随情報を付けます.

![ID00.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/27065/7544446f-6022-0027-7f72-7ab418d0156a.jpeg)

カウンタはIDが再利用された場合の簡易的なチェックのために使用します. 同じIDでアクセスしようとしたとき, カウンタの値が異なれば古い無効なIDです.
全体がuint32_tならIDは16 bitsになりますから, Unityの同時に作成できる最大GameObject数が65536な理由かもしれません. GameObjectを大量に作る状況は確実に設計失敗といえるため, 16 bitsで問題ありません.

# 参照

- [1] http://entity-systems.wikidot.com/
- [2] http://bitsquid.blogspot.com/2009/09/welcome-to-bitsquid-blog.html
- [3] "コンポーネントベースのオブジェクト管理" Game Programming Gems 5
- [4] "ゲームオブジェクトコンポーネントシステム" Game Programming Gems 6

# 感想

Unity開発者は, ECSもOOPの中のデザインパターンという理解はしているはずで, なぜかOOPと違う概念だとかいう誤解が生まれる原因は "Unity Japan" にあると思います.