「デモとしてのECS」が欲しいなら使いませんが「ゲームとしてのECS」が必要なら知っておくと良い、複数のWorld運用についてです。
ECSを”ゲーム”にするためには
ECSをヘビーに使用する時に気になってくるのが「特定の状態で不要になるSystem」の存在です。
デモであればSystemを止める事は不要です。始まりも終わりもなく単純に続けるシステムです、なんにも問題はありません。何故なら全て必要なSystemのみが動作しているからです。
ですが「ゲーム」ならば、Systemが動きっぱなしというのは少し面倒な問題です。大抵の場合、不要なSystemが登場します。
例えばシューティングゲームに必要なPositionの更新やHitcheckといったSystemはゲーム以外の状態…例えば結果画面やタイトル、ポーズ、会話といったシーンでは不要でしょう。
ここでのSystemを止めるアイディアは3つあります。
- 複数のWorldを運用し、Worldを切り替えて運用する
- ComponentSystemを取得して積極的にOFFにする
- Systemが要求する全てのEntityを削除する
ある意味どれでも良いと思いますが、今回は複数のWorldで運用する方法について考えてみます。
複数のWorldを管理する
複数のWorldで運用するのは、個人的には悪くないアイディアのように考えています。
- 有効・無効を大きな単位で行える
- 生成・破棄を裏側で行える
- オブジェクトを維持したまま処理だけを止められる
まずWorldという単位で処理の有効化・無効化が行えるので、そのWorldでどのような処理が行われているのかを把握せず大きなWorld単位でON/OFFが実行できます。
これは「Game World」や「Menu World」のような単位で区切っておけば、メニュー中にゲームを簡単に止められる事を意味します。
オブジェクトの生成・破棄をバックスレッドで実行できる点も魅力的です。同じスレッドだと同期ポイントが必要になるのですが、Worldを分割しておくことで現在有効ではないWorldならスレッドから操作するといったアイディアが出てきます。
またSceneのように読込-破棄を選択するのではなく、Entityを維持したまま処理のみを停止出来るのも魅力的です。例えばゲーム中にメニュー画面を開いても、ゲームの内容は維持されていますが、ゲームの進行は停止するといった事ができそうです。
tsubakit1.hateblo.jp
複数のWorldは動かせなくなりました。
なおWorldは複数同時に動かす事も可能です。
下のGifアニメでは、赤いコインを作成したWorldと白いコインを作成したWorldを作成して、各Worldを有効・無効を切り替えています。
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
を定義しておきます。
初期化を防止するコードはAutomaticWorldBoostrap(PackageManagerから参照)にて確認出来ます。
Worldの破棄はWorld.DisposeAllWorlds();
でも可能ですが、シンボルで事前に作成を抑制したほうが正しい印象です。
Default Worldは「全てのSystemを登録」してしまいます。ちゃんとInjectで注入する形で設計し、注入対象のEntityが無ければそこまでは問題にはならないと思います。
ですがInject
を行わないタイプ(GetEntity等)のSystemや、Systemを別のアプローチで動かす(RXやAsync等)場合には注意が必要かもしれません。
EntityはWorldを跨がず、SystemはWorld毎に作成
Worldを複数作成した時に気になるポイントは、別のWorldからEntityへアクセスすることが出来ないという点です。つまり、異世界転生(World間の移動)は可能ですが、異世界通信は不可能という話。
ただしSystem間の場合は普通にアクセスできそうです。流石にInject
は使えませんが、CreateManager
してるなら普通にイベント登録するなり色々と。
ECSのSystemは普通にオブジェクト指向です。
Systemの停止処理をはさみたい場合は注意
なお、今回の方法で止まるのはOnUpdate
等の処理です。
OnStopRunningやOnStartRunning
等のコールバックが呼ばれる訳ではない点に注意です。
またOnUpdateで駆動してない場合もあります。
例えばMeshInstanceRendererSystem
とかはRenderPipeline.beginCameraRendering
や Camera.onPreCull
側にイベント登録して呼び出しを行っているので、Worldを止めても無効化されません。
明確に停止したい場合は、World.GetExistingManager
でSystemを取得して停止するのが良さそうです。
関連
ECSで移動するビルボード風スプライトを同時に114514体表示する
【Unity】Entity Component System入門(その1)【2018.2】
【Unity】ECS + JobSystemでライフゲームを実装してみた