テラシュールブログ

旧テラシュールウェアブログUnity記事。主にUnityのTipsやAR・VR、ニコニコ動画についてのメモを残します。

【Unity】Unity 2018のEntity Component System(通称ECS)について(2)

Entity Component System(通称ECS)のUnityでの使い方について書いていきます。
今回の内容は、Unityの提供するECS APIの基本的な使い方、少しの応用、並列化までです。

UnityのGameObject/Componentとの連携、あと実際の使い方的なものは含みません。正直な話、既存のGameObjectとの連携系(通称ハイブリット)は変化する気配がビンビンするので、もう少し安定したら書きます。

 

 なお、ECSはまだ開発者向けビルドが公開された段階なので、将来的には実装方法が変化している可能性があります。

 

新しいAPIがいくつか増え、この使い方は若干古いアプローチになりました。

 

 

ECSを使えるエディターの入手

まずはECSが利用できるビルドを入手します。

https://github.com/Unity-Technologies/EntityComponentSystemSamplesDownload the beta build requested hereよりダウンロードリンクへ飛べます。

後は自分のOSと一致するエディターをダウンロードして、インストールすればOKです。その際にはビルド機能は不要なので、WebGLiOSといったアドオンは不要です。

f:id:tsubaki_t1:20180402193000j:plain

ECSが使えるプロジェクトを作成

プロジェクトを作成しますが、まだECSを使用できません。
ECSを使用可能にするには、大きく分けて二つの事が必要です。

  • .NET を4.xにする
  • manifest.jsonを書き換える

まず普通にUnityを起動して、Edit > PlayerSettings > PlayerSettingsを開きます。
後はScripting Runtime VersionをStable (.net 4.x)に変更します。

f:id:tsubaki_t1:20180402193537j:plain

次にmanifest.jsonの書き換えです。
プロジェクトのRootフォルダ/Packagesに manifest.jsonというファイルがあるので、中身をココと同じように書き換えます。

github.comこれで準備は完了です。

最小限のECSプロジェクト

まずは可能な限り最小限の、あまり意味のない機能を組んでみます。
今回組む機能はこんな感じ

  • カウントを毎フレーム足す

まずはMonoBehaviourで組んでみます。とても簡単なコードです。
組んだらCounterコンポーネントを適当なGameObjectに追加すれば良いです。

gist.github.com

次に、これをECSに対応させます。やる事は三つ
ん?面倒くさい? ECSはそういうものです(暴論

  • カウントの値を持つCountData
  • 実際にカウントするCountSystem
  • Entityを作るECSMain

gist.github.com

f:id:tsubaki_t1:20180402194500j:plain

後はECSMainを適当なオブジェクトにアタッチして再生すれば、最初のステップは完了です。
再生中Window > EntityDebuggerを開いて、Systems一覧からCountSystemを見つけた際に白くなっていて、かつEntityが存在すれば成功しています。

f:id:tsubaki_t1:20180402195921g:plain

もしEntityが無ければ、CountDataが無いEntityを作っているか、そもそもEntityを作っていない可能性があります。また、もしSystemが無いならばComponentSystemを作成するコードに何かミスがあります。

 

少し解説

少し中身を見てみます。

ECSの各役割については、下の図の通り。

f:id:tsubaki_t1:20180325104607j:plain

さて、まずはComponentData、ここで重要なのはstruct(構造体)である事と、IComponentDataを継承していることです。
ComponentDataとして使用する場合には、必ずこの二つが必要になります。

f:id:tsubaki_t1:20180402202139j:plain

Entityを作成するECSMainは、これはEntityの作成のキッカケとなる訳ですが、MonoBehaviour系イベントとかから呼び出します。

中身はコメントに書いてあるとおり、

  1. EntityManagerを取得
  2. Entityのアーキタイプを作る
  3. アーキタイプ情報を元にEntityを作成

といった流れです。

アーキタイプにComponentDataを格納する順番とか、作るタイミングとかは特に考えなくとも良いみたいです。重要なのは「何が入っているのか」という点のみ。

 

最後はComponentSystemとして用意しているCountSystemについてです。これは二部構成になっています。

  • Groupの定義
  • 処理の内容

まずはGroupの定義と実際に注入先を用意するコードです。

ComponentDataArrayは要求するコンポーネント(のポインタ)、Lengthには要求するComponentDataを持つEntityの数が格納されます。

あとは[Inject]で、要求するGroupへの参照が自動的に注入されます。

f:id:tsubaki_t1:20180402202559j:plain

下半分…OnUpdate()の部分では、単純に数字を足します。groupの中身は構造体なので、直接代入出来ない点に注意です。

f:id:tsubaki_t1:20180402202956j:plain

システムを増やす例

次にシステムを増やしてみます。

処理の内容は単純に、カウンターが範囲の外に出たら削除する…という感じです。
機能説明のために若干効率は度外視します。

f:id:tsubaki_t1:20180402220344j:plain

ECSは基本的にComponentDataにSystemが何か手を加えて、それを次のシステムに渡す…というベルトコンベアー的な流れ作業で構成されています。

なので「カウンターを足して、カウンターが範囲の外に出たら削除」の場合は

  • 流れてくる「カウンターを含むEntityというコンテナ」に対して
  • 「カウンターを足す」作業をするシステム
  • 「カウンターをチェックして、駄目なら破棄」というシステム

で構成されます。

f:id:tsubaki_t1:20180325103742j:plain

これを実現するために、今回も幾つか追加します。

  • 範囲を取得する共通ComponentData
  • カウントが範囲外に出たらEntityを削除するシステム
  • CountSystem同士の依存関係

f:id:tsubaki_t1:20180402220836j:plain

コードはこちら。

gist.github.com

解説

さて、解説タイムと行きましょう。

まずは範囲判定要のComponentDataであるRangeDataを追加しました。
ただ、この「レンジの情報」は殆どのデータで同じ値を持ってるので、複数のEntity同士で共通するIShardComponentDataを使用しています。
またInspectorでセットアップ出来るようにSerializable属性も追加しました。

f:id:tsubaki_t1:20180402221522j:plain

ECSMainではこのCountDataを追加しています。

まず①ではRangeDataをInspectorに表示して編集可能にして、②でアーキタイプに含める型としてRangeDataを追加して、③で作成したEntityにRangeDataをセットしています。

後は、ボタンを押したらEntityを作るタイプに変更しています。

f:id:tsubaki_t1:20180402221451j:plain

最後にシステムです。
範囲外に出たらEntityを消す…というシステムですが、当然「範囲」を取得する必要があります。なので、②でRangeDataをグループの中に含めています。

また③ではDestroyEntityでEntityを削除しています。

ここで目につくのは、①のUpdateAfterと[ReadOnly]です。この二つは大体同じ様な用途で、[ReadOnly]があると[WriteOnly]等を使用するシステムの後に呼び出されるらしいです。また同様にUpdateAfterがあれば、指定したシステムの後に呼び出されます。

f:id:tsubaki_t1:20180402222641j:plain

f:id:tsubaki_t1:20180402223536j:plain

 

並列化

今回の最後に、ECSの処理を並列化させてみます。対象はカウントを増やすやつです。

並列化は処理負荷がそこそこ長い場合や、モノが大量にある場合に良い結果になる事があります。

gist.github.com

下の図はだいたい6000個のEntityを処理する時間の比較です。並列化とBurstを組み合わせてるので、大分差があります。

f:id:tsubaki_t1:20180402231241j:plain

f:id:tsubaki_t1:20180402231429j:plain

ただし、この並列処理というやつは使えるデータや挙動に限りがあるので、並列化は使えない所もけっこうあります。場合によってはC# Job Systemも使えばもう少し色々出来るかもしれませんが…

 

関連

tsubakit1.hateblo.jp