Entity Component System(通称ECS)のUnityでの使い方について書いていきます。
今回の内容は、Unityの提供するECS APIの基本的な使い方、少しの応用、並列化までです。
UnityのGameObject/Componentとの連携、あと実際の使い方的なものは含みません。正直な話、既存のGameObjectとの連携系(通称ハイブリット)は変化する気配がビンビンするので、もう少し安定したら書きます。
なお、ECSはまだ開発者向けビルドが公開された段階なので、将来的には実装方法が変化している可能性があります。
新しいAPIがいくつか増え、この使い方は若干古いアプローチになりました。
ECSを使えるエディターの入手
まずはECSが利用できるビルドを入手します。
https://github.com/Unity-Technologies/EntityComponentSystemSamples のDownload the beta build requested hereよりダウンロードリンクへ飛べます。
後は自分のOSと一致するエディターをダウンロードして、インストールすればOKです。その際にはビルド機能は不要なので、WebGLやiOSといったアドオンは不要です。
ECSが使えるプロジェクトを作成
プロジェクトを作成しますが、まだECSを使用できません。
ECSを使用可能にするには、大きく分けて二つの事が必要です。
- .NET を4.xにする
- manifest.jsonを書き換える
まず普通にUnityを起動して、Edit > PlayerSettings > PlayerSettingsを開きます。
後はScripting Runtime VersionをStable (.net 4.x)に変更します。
次にmanifest.jsonの書き換えです。
プロジェクトのRootフォルダ/Packagesに manifest.jsonというファイルがあるので、中身をココと同じように書き換えます。
github.comこれで準備は完了です。
最小限のECSプロジェクト
まずは可能な限り最小限の、あまり意味のない機能を組んでみます。
今回組む機能はこんな感じ
- カウントを毎フレーム足す
まずはMonoBehaviourで組んでみます。とても簡単なコードです。
組んだらCounterコンポーネントを適当なGameObjectに追加すれば良いです。
gist.github.com
次に、これをECSに対応させます。やる事は三つ
ん?面倒くさい? ECSはそういうものです(暴論
- カウントの値を持つCountData
- 実際にカウントするCountSystem
- Entityを作るECSMain
後はECSMainを適当なオブジェクトにアタッチして再生すれば、最初のステップは完了です。
再生中にWindow > EntityDebuggerを開いて、Systems一覧からCountSystemを見つけた際に白くなっていて、かつEntityが存在すれば成功しています。
もしEntityが無ければ、CountDataが無いEntityを作っているか、そもそもEntityを作っていない可能性があります。また、もしSystemが無いならばComponentSystemを作成するコードに何かミスがあります。
少し解説
少し中身を見てみます。
ECSの各役割については、下の図の通り。
さて、まずはComponentData、ここで重要なのはstruct(構造体)である事と、IComponentDataを継承していることです。
ComponentDataとして使用する場合には、必ずこの二つが必要になります。
Entityを作成するECSMainは、これはEntityの作成のキッカケとなる訳ですが、MonoBehaviour系イベントとかから呼び出します。
中身はコメントに書いてあるとおり、
といった流れです。
アーキタイプにComponentDataを格納する順番とか、作るタイミングとかは特に考えなくとも良いみたいです。重要なのは「何が入っているのか」という点のみ。
最後はComponentSystemとして用意しているCountSystemについてです。これは二部構成になっています。
- Groupの定義
- 処理の内容
まずはGroupの定義と実際に注入先を用意するコードです。
ComponentDataArrayは要求するコンポーネント(のポインタ)、Lengthには要求するComponentDataを持つEntityの数が格納されます。
あとは[Inject]で、要求するGroupへの参照が自動的に注入されます。
下半分…OnUpdate()の部分では、単純に数字を足します。groupの中身は構造体なので、直接代入出来ない点に注意です。
システムを増やす例
次にシステムを増やしてみます。
処理の内容は単純に、カウンターが範囲の外に出たら削除する…という感じです。
機能説明のために若干効率は度外視します。
ECSは基本的にComponentDataにSystemが何か手を加えて、それを次のシステムに渡す…というベルトコンベアー的な流れ作業で構成されています。
なので「カウンターを足して、カウンターが範囲の外に出たら削除」の場合は
- 流れてくる「カウンターを含むEntityというコンテナ」に対して
- 「カウンターを足す」作業をするシステム
- 「カウンターをチェックして、駄目なら破棄」というシステム
で構成されます。
これを実現するために、今回も幾つか追加します。
- 範囲を取得する共通ComponentData
- カウントが範囲外に出たらEntityを削除するシステム
- CountSystem同士の依存関係
コードはこちら。
解説
さて、解説タイムと行きましょう。
まずは範囲判定要のComponentDataであるRangeDataを追加しました。
ただ、この「レンジの情報」は殆どのデータで同じ値を持ってるので、複数のEntity同士で共通するIShardComponentDataを使用しています。
またInspectorでセットアップ出来るようにSerializable属性も追加しました。
ECSMainではこのCountDataを追加しています。
まず①ではRangeDataをInspectorに表示して編集可能にして、②でアーキタイプに含める型としてRangeDataを追加して、③で作成したEntityにRangeDataをセットしています。
後は、ボタンを押したらEntityを作るタイプに変更しています。
最後にシステムです。
範囲外に出たらEntityを消す…というシステムですが、当然「範囲」を取得する必要があります。なので、②でRangeDataをグループの中に含めています。
また③ではDestroyEntityでEntityを削除しています。
ここで目につくのは、①のUpdateAfterと[ReadOnly]です。この二つは大体同じ様な用途で、[ReadOnly]があると[WriteOnly]等を使用するシステムの後に呼び出されるらしいです。また同様にUpdateAfterがあれば、指定したシステムの後に呼び出されます。
並列化
今回の最後に、ECSの処理を並列化させてみます。対象はカウントを増やすやつです。
並列化は処理負荷がそこそこ長い場合や、モノが大量にある場合に良い結果になる事があります。
下の図はだいたい6000個のEntityを処理する時間の比較です。並列化とBurstを組み合わせてるので、大分差があります。
ただし、この並列処理というやつは使えるデータや挙動に限りがあるので、並列化は使えない所もけっこうあります。場合によってはC# Job Systemも使えばもう少し色々出来るかもしれませんが…