簡単なサンプルではなく、ECSを大規模に使用した技術デモ「Unite Austin Technical Presentation」の紹介です。
Unite Austin Technical Presentation
Unite Austin Technical Presentationは、Nordeus社が作成したゲームの一部プレビューです。内容は大量のミリオン同士の戦い…という感じです。
実際、数万体*1のミリオンがワールドの中央付近で戦闘を繰り広げています。
また1キーや2キーでワールドの中身に介入することも出来ます。
例えばマウスの位置に隕石?を落下する攻撃を打ち込むと、大量のミリオンが一斉に吹き飛ぶのが確認出来ます。
— 椿 (@tsubaki_t1) 2018年4月26日
プロジェクトは以下のGithubから入手出来ます。サイズがちょっと大きくて、3.3GBくらいあるので、モバイルWifiから落とす場合には注意が必要です。帯域制限を喰らいます(体験談
ユニットの動作や攻撃にはECSが使用されている
このゲームですが、キャラクターの移動や攻撃、キャラクター表現等、多くの部分にECSが使用されています。
キャラクター毎にEntityが割り当てられ、キャラクターの一体一体が各々で動いています。ミリオンだけでなく、弓の攻撃も一本一本がEntityです。
ステージとの連携は、ふっ飛ばされた時などの物理挙動にはRaycastCommandで地面との相対距離等が求められていますが、移動はもっぱらNavMeshQueryでパスを取得してパスの通りに移動するというものみたいです。
逆に、判定やレンダリングなど、ユニットの動作に関する部分は多くの部分が自作コードになっていました。これは単純に用意されてないというのもありそうですが、幾つかは特別な最適化を施したいというのも理由の一つかもしれません。
例えば以下のような感じ
これらの処理は大まかな部分をSystemはJobSystemに移譲しており、ワーカースレッドはソレナリの範囲を埋めています。
特に長いのはMoveJobで、中でNavMeshQueryでパス計算とかしてます。
キャラクターアニメーションは頂点シェーダー
さて、このプロジェクトは数万体という大量のユニットを同時に表現するためにDrawMeshIndirectでキャラクターを描画しています。
とは言え、DrawMeshIndirectで描画するものは基本的にアニメーションを使用できません。その辺り、このプロジェクトでは、頂点シェーダーでポリゴンを動かすことで実現しています。
基本的には Animation Texture Baker と同じ系統の技術のようですが、テクスチャをベイクするというよりはバッファを云々してるように見えます。(ちゃんとコードを読んでいない)。このバッファを塗るのに、UnsafeUtilityもガンガン使うという中々に面白くないコードになっています。
なお、何故かTextureAnimationSystemはComponentフォルダ以下にあります。多分ミスです。Systemフォルダに無かったので焦りました。
状態の切り替えはComponentDataの追加で
ユニットには複数の状態が設定されています。例えば「移動中」とか「攻撃中」とか、あとは「死亡中」とか。
この状態の切替は、ComponentDataの抜き差しで行っていました。
ComponentDataの抜き差し(AddComponent/RemoveComponent)は、ComponentDataが所属するチャンクを移動する事になるので余り効率的では無いように思ってたんですが、各動作毎に要求するデータが大きく違うので、その辺りを嫌がったのかもしれません。
まあチャンク間の移動といっても、数箇所の要素を塗り直すだけですし(新しくチャンクが作られなければ)案外問題は無いのかもしれません。
ユニット情報はPrefabから取得
ECSを使用しているので殆どがコードで書かれるように思っていましたが、ユニット情報等はECSハイブリットの構造を使用して取得していました。
Prefab(GameObject)をInstantiateするのではなく、PrefabからEntityを作ってEntityをInstantiateする感じです。
これでコードを弄る事なく、複数パターンのEnittyを作成しています。
このコードを見てEntityManagerのInstantiateにGameObject割り当てられる事に気づきました。
確かにこの使い方は理にかなってるように感じます。まぁ最終的にはScriptableObjectかEntityをシリアライズしたものに纏めてほしい気もします。
ユニットの生成位置はMonobehaviourから取得
ハイブリットと言えば、ユニットの生成位置等の情報はMonoBehaviourを継承したコンポーネントから取得していました。
出現位置などの情報はGameObjectでシーン上に配置(Gizmoでプレビュー)し、実際にUnitを生成するのはComponentSystemで行っている感じです。
関連
このプロジェクトの紹介です
ECSとGameObjectのハイブリット
tsubakit1.hateblo.jpECSについて
このプロジェクトのフォーラム
https://forum.unity.com/threads/unite-austin-technical-presentation.522628/
*1:ゲーム開始時は1万くらい、放置すると2万3万と増えていく