【Unity】ECSでSystemの有効化・無効化についてのまとめ
ECSの基本はSystemです。システムというWorld毎にユニークなオブジェクトが多くのEntityを操作することでゲーム表現を実現しています。
ここで気になるのはSystemを動かす方法、及び止める方法についてです。今回はその辺りについて書いてみます。
Default WorldのSystemは自動的に登録される
まずECSのSystemですが、特に何も指定しなければ自動的に生成されるDefault Worldには全てのSystemがゲームに登録されます。
開発者は動作を確認するというフェーズではSystemの運用を特に気にせず機能と動作の開発に集中出来る感じです。
自動的に登録したくない場合
ただし特定のシステムを最初から登録したくないといったケースもあります。そういった場合には[DisableAutoCreation]
をシステムに設定しておくとSystemが自動的に生成されなくなります。
このアプローチは一つのWorldでゲームを作る場合に作りやすくなるかなと思います。
一方、複数のWorldで運用が前提になってくるとDefault Worldの存在が少し厄介です。新しいWorldには全てのSystemが自動的に登録されていないので、自分でSystemを登録する必要が出てきます。そうすると、登録の要不要で一貫性がなくなります。
そういった場合、UNITY_DISABLE_AUTOMATIC_SYSTEM_BOOTSTRAP
を使用することで、初期のWorld生成を抑制し、実装に一貫性を持てます。え?逆に全てのWorldに全Systemを登録すれば良い?それはDefaultWorldInitializationのアクセスが取れないから面倒かなぁ…
Systemの明示的な作成
Systemは World.Active.GetOrCreateManager<EntityManager>()
で明示的に作成できます。 World.Active.CreateManager<EntityManager>()
もありますが、既にEntityが存在すると問題になるので、個人的には「明示的な作成」のような生成タイミングがフワっとしてるものにはオススメしません。(Worldの初期化時に作るんだ!!!等ならCreateManagerでも)
生成時にはOnCreateManager
、削除時にはOnDestroyManager
のコールバックが呼ばれます。NativeArrayを作る時はココでやるのが良さそうな感じがします。
コンストラクタも使えますが引数を持っている場合、World.Active.CreateManager
以外のタイミングで生成されると破綻するので余り良い選択肢ではない印象です。(個人で作ってるなら問題ないでしょうが…)
作成後はScriptBehaviourUpdateOrder.UpdatePlayerLoop(World.Active);
を呼び出します。これをしないとOnUpdateは動作しません。削除後も同様で、忘れると下のようなエラーが出続けます。
NullReferenceException: Object reference not set to an instance of an object
Unity.Entities.ComponentSystemBase.BeforeUpdateVersioning () (at Library/PackageCache/com.unity.entities@0.0.12-preview.16/Unity.Entities/ComponentSystem.cs:158)
Unity.Entities.ComponentSystem.BeforeOnUpdate () (at Library/PackageCache/com.unity.entities@0.0.12-preview.16/Unity.Entities/ComponentSystem.cs:315)
Unity.Entities.ComponentSystem.InternalUpdate () (at Library/PackageCache/com.unity.entities@0.0.12-preview.16/Unity.Entities/ComponentSystem.cs:355)
Unity.Entities.ScriptBehaviourManager.Update () (at Library/PackageCache/com.unity.entities@0.0.12-preview.16/Unity.Entities/ScriptBehaviourManager.cs:77)
Unity.Entities.ScriptBehaviourUpdateOrder+DummyDelagateWrapper.TriggerUpdate () (at Library/PackageCache/com.unity.entities@0.0.12-preview.16/Unity.Entities/ScriptBehaviourUpdateOrder.cs:703)
また[Inject]で特定のシステムを参照した場合も自動的に生成されるみたいです。こちらはScriptBehaviourUpdateOrder.UpdatePlayerLoop
を実行しなくても自動的に登録されます。
Systemの明示的な有効化・無効化
Systemを明示的に有効化・もしくは無効化します。
やり方は単純で、WorldからGetExistingManager
でSystemを取得して、Enabled
をtrue
もしくはfalse
にするだけです。
なおMonobehaviourではなくComponentSystemから取得する場合、World.GetExistingManager<System名>()
で同じWorldのシステムを取得出来ます。まぁソレ以前に[Inject]System名 sys;
で呼べるんですが
このアプローチで有効化・無効化を設定した場合、Entity Debuggerでチェックボックスが外れます。また有効化・無効化時にOnStartRunning
とOnStopRunning
のコールバックが呼ばれます。
アクセスするEntityが無いSystemは動作しない
小まめにSystemをON/OFFしたいところですが、それ程頑張らなくても良いという意見もあります。というのも、Systemは要求するGroupが存在しない場合は動作が停止し、OnUpdate等で呼ばれる処理は呼ばれなくなります。
なお動作しないといってもSystemの数だけ呼び出し処理は走ります(OnUpdateが呼ばれる訳ではありません)。一つ一つは本当に0ms~と殆ど負荷にならないと思いますが、量が増えてくると負荷になるかもしれません。ただコレに目を向けるのは早すぎる最適化な感じがします。
常に動かしたい場合
少々厄介なのがこのSystemが複数のGroupを要求した場合の判定はORなので、どれか一つでも条件が揃えば呼ばれてしまう点です。Systemが2つのGroupを要求していた場合、どちらかが揃えばSystemは動作してしまいます。
またSystemが何も要求していない場合も、自動的に動作するようになっています。
下のコードは配置しておけば常にDo it!!!と叫ぶ迷惑なコードです。
Groupが何らかコンポーネントを要求しつつも常に動かしたい…という場合にはAlwaysUpdateSystem
を使用します。当然要求したグループが空の可能性もあるので、そこは自分でなんとかする必要があります。