テラシュールブログ

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

【Unity】ECSで複数のWorldを運用する方法

「デモとしてのECS」が欲しいなら使いませんが「ゲームとしてのECS」が必要なら知っておくと良い、複数のWorld運用についてです。

 

 

ECSを”ゲーム”にするためには

ECSをヘビーに使用する時に気になってくるのが「特定の状態で不要になるSystem」の存在です。

デモであればSystemを止める事は不要です。始まりも終わりもなく単純に続けるシステムです、なんにも問題はありません。何故なら全て必要なSystemのみが動作しているからです
ですが「ゲーム」ならば、Systemが動きっぱなしというのは少し面倒な問題です。大抵の場合、不要なSystemが登場します
例えばシューティングゲームに必要なPositionの更新やHitcheckといったSystemはゲーム以外の状態…例えば結果画面やタイトル、ポーズ、会話といったシーンでは不要でしょう。

ここでのSystemを止めるアイディアは3つあります。

  • 複数のWorldを運用し、Worldを切り替えて運用する
  • ComponentSystemを取得して積極的にOFFにする
  • Systemが要求する全てのEntityを削除する

ある意味どれでも良いと思いますが、今回は複数のWorldで運用する方法について考えてみます。

f:id:tsubaki_t1:20180923022709p:plain

 

複数のWorldを管理する

複数のWorldで運用するのは、個人的には悪くないアイディアのように考えています。

  • 有効・無効を大きな単位で行える
  • 生成・破棄を裏側で行える
  • オブジェクトを維持したまま処理だけを止められる

まずWorldという単位で処理の有効化・無効化が行えるので、そのWorldでどのような処理が行われているのかを把握せず大きなWorld単位でON/OFFが実行できます
これは「Game World」や「Menu World」のような単位で区切っておけば、メニュー中にゲームを簡単に止められる事を意味します。

オブジェクトの生成・破棄をバックスレッドで実行できる点も魅力的です。同じスレッドだと同期ポイントが必要になるのですが、Worldを分割しておくことで現在有効ではないWorldならスレッドから操作するといったアイディアが出てきます。

またSceneのように読込-破棄を選択するのではなく、Entityを維持したまま処理のみを停止出来るのも魅力的です。例えばゲーム中にメニュー画面を開いても、ゲームの内容は維持されていますが、ゲームの進行は停止するといった事ができそうです。

f:id:tsubaki_t1:20180923024530p:plain

tsubakit1.hateblo.jp

なおWorldは複数同時に動かす事も可能です。
下のGifアニメでは、赤いコインを作成したWorldと白いコインを作成したWorldを作成して、各Worldを有効・無効を切り替えています。

f:id:tsubaki_t1:20180923022030g:plain

実際にやり方を見ていきます。

 

Worldを作成する

まずは独自のWorldを作成します。

Worldはnew World("World Name");で作成できます。
なお独自に作成したWorldには一切のSystemが登録されていないので、world.CreateManager<ComponentSystem>();で必要なシステムは独自に登録する必要があります。

gist.github.com 

動かすWorldを切り替える

次に複数作成したWorldを切り替えます。
正確にはWorldを有効化するという方が動作に沿っています。

ScriptBehaviourUpdateOrder.UpdatePlayerLoopを使用すると、指定したWorldのみが有効化します。逆を言えば、指定していないWorldの持つ全てのSystemは動作を停止します。

gist.github.com

Tips

World.Active

WorldにはActiveというAPIがあります。
これは特別な意味はない単なるプロパティです。そのため、ココにWorldをセットする事に意味はそれ程ありません。

強いて言うならSceneのActiveと同じような扱いになりそうです。つまりGameObjectを作成したらActiveなシーンに生成されるように、アクティブなシーンに対して色々と行う…という感じ。

Worldの運用によっては、メインのゲームとなるWorldは常に一つということも考えられるので(Worldを跨いだEntityのやりとりが出来ないので、メインとなるWorldへ追加したEntityをマージしていく)、こういった設計も有りかなという感じです。

 

Default Worldを生成しない方法

コレを使っていると気になるのが、最初から用意されているWorldです。
最初から用意されているWorldは基本的に全てのSystemが最初から登録されています。処理の一貫性を保つ為には、コレは余計なお世話です。

Default Worldを作成しないようにするには、Scripting Define Symbolsに UNITY_DISABLE_AUTOMATIC_SYSTEM_BOOTSTRAPを定義しておきます。

f:id:tsubaki_t1:20180923024034j:plain

初期化を防止するコードはAutomaticWorldBoostrap(PackageManagerから参照)にて確認出来ます。
Worldの破棄はWorld.DisposeAllWorlds();でも可能ですが、シンボルで事前に作成を抑制したほうが正しい印象です。

f:id:tsubaki_t1:20180923023831j:plain

Default Worldは「全てのSystemを登録」してしまいます。ちゃんとInjectで注入する形で設計し、注入対象のEntityが無ければそこまでは問題にはならないと思います。
ですがInjectを行わないタイプ(GetEntity等)のSystemや、Systemを別のアプローチで動かす(RXやAsync等)場合には注意が必要かもしれません。

 

EntityはWorldを跨がず、SystemはWorld毎に作成

Worldを複数作成した時に気になるポイントは、別のWorldからEntityへアクセスすることが出来ないという点です。つまり、異世界転生(World間の移動)は可能ですが、異世界通信は不可能という話。

f:id:tsubaki_t1:20180923031357p:plain

ただしSystem間の場合は普通にアクセスできそうです。流石にInjectは使えませんが、CreateManagerしてるなら普通にイベント登録するなり色々と。
ECSのSystemは普通にオブジェクト指向です。

 

Systemの停止処理をはさみたい場合は注意

なお、今回の方法で止まるのはOnUpdate等の処理です。
OnStopRunningOnStartRunning等のコールバックが呼ばれる訳ではない点に注意です。

またOnUpdateで駆動してない場合もあります。
例えばMeshInstanceRendererSystemとかはRenderPipeline.beginCameraRenderingCamera.onPreCull側にイベント登録して呼び出しを行っているので、Worldを止めても無効化されません。

 明確に停止したい場合は、World.GetExistingManagerでSystemを取得して停止するのが良さそうです。

 

関連

ECSで移動するビルボード風スプライトを同時に114514体表示する

【Unity】Entity Component System入門(その1)【2018.2】

【Unity】ECS + JobSystemでライフゲームを実装してみた