エンティティ・コンポーネント・システム
エンティティ・コンポーネント・システム(Entity component system、ECS)とは、主にゲーム開発で使用されているソフトウェアアーキテクチャパターンである。ECSは継承よりコンポジションの原則に従うことで、より柔軟にエンティティを定義することを可能にする。エンティティとは、ゲームのシーンの中のすべての実体であるオブジェクトのことである(例えば、敵、銃弾、乗り物など)。すべてのエンティティは、付加的な振る舞いや機能を追加するものである1つ以上のコンポーネントから構成される。したがって、エンティティの振る舞いは、実行時にコンポーネントを追加あるいは削除することで変更可能である。これは、深く幅広い継承階層を除去し、その理解・保守・拡張が難しくなりあいまいになるという問題を取り除く。ECSの一般的なアプローチはデータ指向設計の手法と高い互換性を持ち、よく組み合わせられる。
1998年のゲーム Thief: The Dark Project は、公に知られているECSを使った最古のゲームである。ただし、技術的詳細の公開は他よりもずっと行われていない。
2002年のゲーム ダンジョン・シージ は、初めてECSの実装について明快に説明したものの1つである。同年のGDCにおいて、Scott Bilasが講演を行った記録がある。
Bilasの講演はコンポジションのためのデータベースの今日の標準的な概念となっている、「データ駆動」スキーマについて明確に言及した。それは単なるプロパティでなく、「自己充足」のアイテムとしてのコンポーネントなどである。
2007年、Operation Flashpoint: Dragon Rising の開発に携わっているチームが、ECSの設計の実験を行った。Bilas(ダンジョン・シージ)と、Adam Martinが後に書いた詳細なECSの解説に触発された。Martinの解説は中心的な用語と概念の定義を含んでいる。特に、Martinのブログは「システム」がファースト・クラスの要素であるとし、「エンティティをIDとして扱う」、「コンポーネントを生のデータとする」、ならびに「コードはシステムに置かれる(コンポーネントやエンティティでなく)」との考えを普及させた。
2008年から2010年にかけて、人気という点で巨大な成長を遂げたUnity ゲームエンジンは、エンティティとコンポーネントを異なる手法で使用し、その手法は有名になり、広く使用された。しかし、Unityは彼らの手法について公式に説明しておらず、「ECS」という単語は、通常は但し書きがない限り2002年のBilasの手法を意味すると仮定される。
2015年、AppleはiOS、macOS、tvOSでゲーム開発用に GameplayKit フレームワークでECSを実装。SpriteKit、SceneKit、XcodeのScene Editorで便利に使用できるよう統合されているが、グラフィックスエンジンとは別に実装されているためゲーム以外でも使用できる。
2018年、Unity 2018でコンポーネント志向とは異なるECSが実装された。
今日広く使用されている、Martinの用語 :
エンティティ: エンティティとは汎用オブジェクトである。通常、1つのユニークなIDのみで構成されるこれらは「すべての粗末なゲームオブジェクトを分離されたアイテムとしてタグ付けする」。実装は一般的に平凡な整数値をこれに使う。
コンポーネント: オブジェクトの1つの様相、あるいはこれが如何にして世界とインタラクトするか、を表す生のデータ。「エンティティをこの特有の様相を持つとラベル付けする」。実装は一般的に、構造体、クラス、あるいは連想配列を使う。
システム: 「各システムは継続的に実行され(あたかも各システムがそれぞれのプライベートスレッドを持つかのように)、そのシステムと同じ様相を持つコンポーネントを持つエンティティすべてに対して包括的な作用を実行する。」
描画関数があると仮定する。
これは物理、ならびに可視のコンポーネントを持つすべてのエンティティをイテレートし、描画する「システム」であると言えなくもない。
可視コンポーネントは典型的に、エンティティがどう見えるべきかについての情報(例えば、人間、怪物、舞う火花、飛ぶ矢など)を持ち、物理コンポーネントはどこに描画するかを知るために使用される。
もう一つのシステムとして衝突検出があり得る。
これは物理コンポーネントを持つ全てのエンティティをイテレートするが、どのようにエンティティが描画されるかについては気にしない。
このシステムはその後、例えば、怪物と衝突する矢を検出し、それが起きた時にイベントを生成するだろう。
矢が他のオブジェクトと当たったとき、矢が何であるかは理解する必要はなく、そのことの意味を理解する必要もない。
さらにもう一つのコンポーネントとして体力データが考えられ、それを管理するシステムも考えられる。
体力コンポーネントは人間と怪物エンティティにアタッチされるべきで、矢エンティティは違うだろう。
体力管理システムは、衝突から生成されるイベントを購読し、それに応じて体力を更新するだろう。
このシステムは、体力コンポーネントを持つすべてのエンティティをイテレートし、体力を再生成すると考えられるだろう。
エンティティはIDとコンポーネントのコンテナのみで構成される。
この考えは、ゲームメソッドをエンティティに埋め込まないようにするためのものである。
コンテナはエンティティと物理的に連続して配置される必要は無いが、発見とアクセスが容易であるべきだ。
各エンティティにユニークなIDをもたせるのが一般的な方法である。
これは必須要件ではないが、以下の優位性を持っている:
エンティティをポインターではなくIDで参照できる。こうすることで、ポインタが不正なメモリ領域を指すことなくエンティティを廃棄することができ、より堅牢である。
状態を外部に保存することが容易になる。状態を再び読み込んだとき、ポインタを再構築する必要がない。
必要に応じて、データのメモリ上の順序を変更できる。
ネットワーク越しに通信する際、エンティティIDを使ってエンティティを指すことができる。
これらの優位性のうちいくつかは、スマートポインタを使用することでも同様に得られる。
ECSによる設計には、いくつかの問題がある。
システム間でデータを送信する普通の方法は、コンポーネント内のデータを格納することである。
例えば、あるオブジェクトの位置は定期的に更新され得る。
その時、この位置が他のシステムに使用されるのである。
たまに起こるそれぞれ異なるイベントが大量にある場合、大量のフラグが1つ以上のコンポーネントに必要となるだろう。
イテレーション毎に、システムはそれらのフラグを監視しなければならず、これは非効率になりうる。
そこで、ある解決策ではObserver パターンを使用する。
イベントに依存するすべてのシステムはそれを購読する。
イベントによるアクションは、そのイベントが発生した時に1度実行されるのみなので、ポーリングは必要ない。
基本的な考え方は、すべてのエンティティの巨大なリストを持つことだ。
各システムは完全なリストをイテレートして、そのシステムにとって必要なエンティティのみを選択する。
システムの数が増え、エンティティの数も増えると、すべてのシステムのイテレートするコストは、合計すると大きすぎになるかも知れない。
そこで、ある解決策ではエンティティのリストのサブセットを作る。
代表的な例として、衝突検出がある。
もしすべてのオブジェクトが他のすべてのオブジェクトに対して判定を行ったら、コストは二次関数的に増えるだろう。
このコストは空間分割と、種類ごとの分離により劇的に減少させられる。
もしこの分離が極まって、各エンティティのタイプごとのリストで行われるなら、その場合ECSパターンはこの問題にうまくフィットしないかもしれない。
ECSアーキテクチャはとても安全に単純な方法で依存性を扱う。コンポーネントは単純なデータバケットなので、依存性が存在しない。各システムは一般的に、それが操作するエンティティが持っている必要があるコンポーネントを登録するだろう。例えば、ある描画システムはモデル、座標変換、ならびに描画可能コンポーネントへ登録するかも知れない。システムがエンティティにそのロジックを実行するとき、各エンティティがそれに必要なコンポーネントを持っているか確かめる。もし持ってないなら、そのエンティティはただスキップされる。複雑な依存性ツリーは必要ない。
ECSアーキテクチャは複雑な継承ツリーよりも、コンポジションを使用する。エンティティは一般的に、IDとアタッチされたコンポーネントから成るだろう。いかなるタイプのゲームオブジェクトも、エンティティに正しいコンポーネントを追加することで作成することができる。これは開発者がいかなる依存性問題も起こさずに、簡単にあるタイプのオブジェクトの機能を他のものに追加することも可能にする。例えば、プレイヤーエンティティは「bullet」コンポーネントを追加されて持つことができ、すると「bulletHandler」システムから操作されるための要求を満たす。この「bulletHandler」システムを実行することで、プレイヤーが物へダメージを与えるという結果になる。