テラシュールブログ

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

【Unity】指定のコードがGCを発生させるかどうかをテストする(AllocatingGCMemory)

GCガベージコレクション)はコードに紛れひっそりと忍び込みます。
今回はソレを見つけるのに便利なAPIが追加されていたので試してみました。

AllocatingGCMemoryでGCの発生を監視する

Unity 2018.3からAllocatingGCMemoryが新しく追加されました。
このコードとAssert.Thatを使用すると、実装の中でGCが発生しているかどうかをテストすることが出来ます。

Unity - Scripting API: AllocatingGCMemoryConstraint

例えば下のコードはマニュアルのコードです。int aを定義し代入する処理がGCを発生するかといった点をテストしています。 f:id:tsubaki_t1:20181002184258j:plain

ここで少し注意したいのが、IsNUnit.Framework.IsではなくUnityEngine.TestTools.Constraints.Isという点です。
何がどう違うのかは確認していませんが、サンプルコードではご丁寧にusing Is = UnityEngine.TestTools.Constraints.Is;でラッピングしてありました。

実際に使ってみる

実際に使ってみます。
当然テストコードなので、ユニットテストにて処理を実装…という感じのことをしています。一応Runtimeに属するAssertなのでDevelopmentBuildで動くんじゃないかな

コードは下のような感じです。

gist.github.com

結果は下のような感じです。
まぁ普通に配列を宣言したりLINQを使用すればGCが発生しますが、普通に足したりキャストしたりする程度ではGCが発生しません。 f:id:tsubaki_t1:20181002185207j:plain

そういえば昔は構造体や変数をキャストしたりボクシングしたりするとゴミが出てたような記憶があるんですが、今確認すると特に出ないですね。はて?

関連

GCを発生させない」というAPIも追加されました。流石に長時間放置するとゲームが死にますが、「アクションが激しくて絶対に止めたくない」という場合に有効かもしれません。

baba-s.hatenablog.com

【Unity】ECSの並列処理(IJobParallelForやIJobForEach系)でEntityCommandBufferを使う

今回はECSで並列処理した結果をEntityCommandBufferから削除する方法についてです。以前は出来なかったのですが、最近出来るようになりました。

 

 

EntityCommandBufferと非同期処理

使用中のEntity郡に対して、非同期(C# Job System)でアクセスする事は望ましくありません。そのためバッファを別途用意し一旦蓄積、後で一気に処理するというアプローチが望ましいです。

そういった事をしたい場合、EntityCommandBufferを使用して一旦バッファに指示を格納、後で一気に処理するといったを使用します。https://cdn-ak.f.st-hatena.com/images/fotolife/t/tsubaki_t1/20180902/20180902194448.png

tsubakit1.hateblo.jp

規模がでかい場合はExclusiveEntityTransactionを使用するのも良いかもしれませんが、細々としたものはEntityCommandBufferでやるべきです。

tsubakit1.hateblo.jp

このEntityCommandBuffer、以前はIJobでは使えてもIJobParallelForでは使えなかったのですが、気づいたら出来るようになっていました。

EntityCommandBufferと並列処理

EntityCommandBufferを使えると言っても直接は使えません。EntityCommandBuffer.Concurrentを経由してアクセスします。EntityCommandBufferSystem.CreateCommandBuffer().ToConcurrent()インスタンスを取得しジョブに流し込み、後はジョブ側でオブジェクトの追加や削除、Entityの設定等を行います。

なおEntityCommandBuffer.Concurrent経由でオブジェクトを操作する際にはユニークなIDが必要です。これはExecuteの引数のint indexを指定しておけばOKです。

下の例では、指定座標に対して当たり判定を行い、接触していたら削除する…といったものです。以前は一旦バッファに格納していましたが、それが無くても動作します。

 

実際に動かした所、ちゃんと当たり判定の計算は非同期で行いつつ、Entityの削除もちゃんと動作しています。

f:id:tsubaki_t1:20181001220311g:plainIJobForEachWithEntity が使えるようになったので、Entity関連に対してもIJobParallelForを使わずComponentDataとEntityが一緒に取れるようになったのは楽で良いです。

コード

gist.github.com

【Unity】"2D向けの簡単な開発環境"を提供する「Unity Playground」プロジェクト

f:id:tsubaki_t1:20181001000137j:plain

 

 

Unity Playgroundは組み合わせで作るプロジェクト

github.comUnity PlaygroundはUnityを学ぶという目的で少し面白いデモです。
これはプログラミング無しに、事前に用意されているコンポーネントの組み合わせで幾つかのゲーム的な表現を再現することが出来るプロジェクトです。
f:id:tsubaki_t1:20181001000926j:plain

面白いのは、提供しているコンポーネント郡が「○○○を作るための機能郡」ではなく組み合わせで様々なゲームのようなものを構築できる点です。

例えば操作方法や、動かすオブジェクトの役割、ゲームプレイの進行やオブジェクトのスポーンといったスクリプトが最初から用意されており、これらの組み合わせでゲームを作っていきます
スクリプトで記述するより遥かに大雑把な機能しか提供していませんが、色々動かしたり試したりというレベルで見ればアリかなという印象です。

f:id:tsubaki_t1:20181001002429j:plain

 

UIが2D向けに簡略化

PlaygroundプロジェクトではUI表示が幾つか簡略化します。

例えばエディターの幾つかのUIを2D向けに差し替え隠蔽(簡略化)されています。Transformは3D用にZ軸がありますが、Playgroundでは省略されます。またRotationがZ軸のみとなります。カメラに至っては背景色とサイズ以外は何もなくなります。

この辺り、学習するための物を出来るだけ少なくしようという意思が見え隠れしています。

f:id:tsubaki_t1:20181001000434j:plain

このUIはメニューからOFFに出来ます。

f:id:tsubaki_t1:20181001003936j:plain

 

インタラクティブな動作

ゲームを作る上で避けては通れないのがアクションと結果です。アイテムを取ったから障害物が排除される、接触したからダメージ、その他諸々。こういったインタラクティブな要素はコンポーネントとして提供されています。
残念ながらフローを制御する機能は提供されておらず、一次的な反応のみが作れます。

f:id:tsubaki_t1:20181001010349j:plain

動作のイベントに関しては幾つかの機能が提供されています。例えば「範囲内に入ったら」とか「キー入力」「一定間隔で」といった具合。

f:id:tsubaki_t1:20181001005413j:plain

このアクションが成立したとき、様々なアクションを起こします。オブジェクトを消したりダイアログを出したり、シーンをロードしたり。

もしくはUnityEventを呼び出すというオプションもあります。音を出したい場合等はそちらが手軽でしょう。

f:id:tsubaki_t1:20181001005604j:plain

その他、もう少し大雑把なレベルでは「オブジェクトの属性」や「ゲームプレイ」といったものがあります。

HPや接触するとダメージを受ける、アイテムや弾丸、倒したらスコアがもらえる、ボタンを押したら弾が出る、時間が立ったら死ぬ等。

f:id:tsubaki_t1:20181001010016j:plain

f:id:tsubaki_t1:20181001010025j:plain

感想

組み合わせで簡単なゲーム表現を作るプロジェクトでした。
この「組み合わせ」ってのが結構バカに出来なくて、一次機能では大した表現は出来なくても無限に組み合わせる事で結構面白い表現が出来たりします。オブジェクトのON/OFF等を組み合わせてフロー的な表現を実現したり。

 

関連

Playgroundプロジェクトの紹介

blogs.unity3d.comマニュアル(英語)Google翻訳様を通すヨロシ

github.com紹介ムービー

www.youtube.com

【Unity】ECSでSystemの有効化・無効化についてのまとめ

ECSの基本はSystemです。システムというWorld毎にユニークなオブジェクトが多くのEntityを操作することでゲーム表現を実現しています。

ここで気になるのはSystemを動かす方法、及び止める方法についてです。今回はその辺りについて書いてみます。

 

 

Default WorldのSystemは自動的に登録される

まずECSのSystemですが、特に何も指定しなければ自動的に生成されるDefault Worldには全てのSystemがゲームに登録されます。

開発者は動作を確認するというフェーズではSystemの運用を特に気にせず機能と動作の開発に集中出来る感じです。
f:id:tsubaki_t1:20180929202848p:plain

 

自動的に登録したくない場合

ただし特定のシステムを最初から登録したくないといったケースもあります。そういった場合には[DisableAutoCreation]をシステムに設定しておくとSystemが自動的に生成されなくなります

 このアプローチは一つのWorldでゲームを作る場合に作りやすくなるかなと思います。

f:id:tsubaki_t1:20180929203121j:plain

一方、複数のWorldで運用が前提になってくるとDefault Worldの存在が少し厄介です。新しいWorldには全てのSystemが自動的に登録されていないので、自分でSystemを登録する必要が出てきます。そうすると、登録の要不要で一貫性がなくなります。

そういった場合、UNITY_DISABLE_AUTOMATIC_SYSTEM_BOOTSTRAPを使用することで、初期のWorld生成を抑制し、実装に一貫性を持てます。え?逆に全てのWorldに全Systemを登録すれば良い?それはDefaultWorldInitializationのアクセスが取れないから面倒かなぁ…

tsubakit1.hateblo.jp

Systemの明示的な作成

Systemは World.Active.GetOrCreateManager<EntityManager>()で明示的に作成できます。 World.Active.CreateManager<EntityManager>()もありますが、既にEntityが存在すると問題になるので、個人的には「明示的な作成」のような生成タイミングがフワっとしてるものにはオススメしません。(Worldの初期化時に作るんだ!!!等ならCreateManagerでも)

 生成時にはOnCreateManager、削除時にはOnDestroyManagerのコールバックが呼ばれます。NativeArrayを作る時はココでやるのが良さそうな感じがします。
コンストラクタも使えますが引数を持っている場合、World.Active.CreateManager以外のタイミングで生成されると破綻するので余り良い選択肢ではない印象です。(個人で作ってるなら問題ないでしょうが…)

f:id:tsubaki_t1:20180929210857j:plain

作成後は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を取得して、Enabledtrueもしくはfalseにするだけです。

なおMonobehaviourではなくComponentSystemから取得する場合、World.GetExistingManager<System名>()で同じWorldのシステムを取得出来ます。まぁソレ以前に[Inject]System名 sys;で呼べるんですが

f:id:tsubaki_t1:20180929211751j:plain

このアプローチで有効化・無効化を設定した場合、Entity Debuggerでチェックボックスが外れます。また有効化・無効化時にOnStartRunningOnStopRunningのコールバックが呼ばれます。

f:id:tsubaki_t1:20180929211907j:plain

 

アクセスするEntityが無いSystemは動作しない

小まめにSystemをON/OFFしたいところですが、それ程頑張らなくても良いという意見もあります。というのも、Systemは要求するGroupが存在しない場合は動作が停止し、OnUpdate等で呼ばれる処理は呼ばれなくなります。

f:id:tsubaki_t1:20180929203542j:plain

 なお動作しないといってもSystemの数だけ呼び出し処理は走ります(OnUpdateが呼ばれる訳ではありません)。一つ一つは本当に0ms~と殆ど負荷にならないと思いますが、量が増えてくると負荷になるかもしれません。ただコレに目を向けるのは早すぎる最適化な感じがします。

常に動かしたい場合

少々厄介なのがこのSystemが複数のGroupを要求した場合の判定はORなので、どれか一つでも条件が揃えば呼ばれてしまう点です。Systemが2つのGroupを要求していた場合、どちらかが揃えばSystemは動作してしまいます。

またSystemが何も要求していない場合も、自動的に動作するようになっています。
下のコードは配置しておけば常にDo it!!!と叫ぶ迷惑なコードです。

f:id:tsubaki_t1:20180929204328j:plain

Groupが何らかコンポーネントを要求しつつも常に動かしたい…という場合にはAlwaysUpdateSystemを使用します。当然要求したグループが空の可能性もあるので、そこは自分でなんとかする必要があります。

f:id:tsubaki_t1:20180929205112j:plain