テラシュールブログ

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

【Unity】Unity 2018のPlayerLoopで、Unityが毎フレーム呼ぶ処理を無効にしたり、Update"前"に独自の処理を追加したり

f:id:tsubaki_t1:20180417225922j:plain

今回はUnity 2018から追加されたPlayerLoopを使用して、Unityのイベント(UpdateやFixedUpdate等、Physicsホニャララ)といった処理に独自のイベントを追加したり、カスタマイズしたりします。

 

Unityが呼ぶイベント

Unity 4やUnity 5の時期は、Unityのイベントは基本的にMonoBehaviourで呼び出す処理はUpdateやLateUpdateといった処理で、その順番はScriptExecutionOrderでセットしない限りは不明でした。
また基本的にComponentの上で動作するのでSceneのセットアップが必須であり、セットアップのみを行うInit Sceneなんてものも登場しました。

途中、RuntimeInitializeOnLoadが登場してシーンに依存しない初期化処理の呼び出しが可能になりました。
ただ基本的には「初回に1回のみ」の呼び出しで、アップデートのような毎フレーム呼び出す処理を実装したい場合はMonoBehaviourを呼び出すなりThread立てる必要がありました。

f:id:tsubaki_t1:20180417231617j:plain

Unity 2018.1では、PlayerLoopと呼ばれる機能が導入されました(現在Experimental)。これはUnityエンジンのアップデート処理の順番を並べ替えたり、特定のイベントを停止したり、独自の処理を差し込むといった事を可能にしてるみたいです。

 

使い方

さっそく使い方を見ていきます。
なお、PlayerLoopはUnity 2018.1現在ではExperimental(実験的)な機能であり、将来的にAPIが変化する可能性があります。少なくとも namespacehaは変化するはずです。

 

標準イベントの一覧を取得する

まずUnityで呼び出される標準イベントを取得してみます。

下のコードをプロジェクトに配置し、ゲームを再生すればUnityが呼び出すループの一覧が取得できます。

gist.github.com

f:id:tsubaki_t1:20180417233202j:plain

 

まずvar playerLoop = PlayerLoop.GetDefaultPlayerLoop();で標準イベントを全て取得します。
重要なのはplayerLoop.subSystemListで、このリストに格納されているメソッドが再帰的に呼ばれます。呼び出しをカスタマイズしたい場合には、このsubSystemListの部分を色々と編集することになります。

 

なお、この部分で取得するメソッドの一覧はUnityEngine.Experimental.PlayLoopで確認できるものと一致するみたいです。

f:id:tsubaki_t1:20180417233804j:plain

gist.github.com

PlayerLoop自体を上書きする

PlayerLoopを僕の考えた最強の処理に差し替えてみます。
正しくは、Unityの基本機能である様々な処理の呼び出しを一旦無かったことにして、そこに自分の処理を差し込みます。

これ自体はものすごく簡単に実現できます。ただし処理の呼び出しを無かったことにする=Unityのループ処理が全て破棄されるので、Unityが利用する殆どの機能が停止します
物理演算は死にますし、入力周りも、UIのアクションも全部死にます。

gist.github.com

またループを無かった事にした影響か、自分の環境だとゲーム再生終了時にエディターが落ちました。正直この「ループ自体上書きする」は良くない使い方です。

 

Update前に独自のイベントを仕込む

Unityのシステムが全て止まってしまっては話になりません。今度はアップデート前に処理を挟む…みたいな事をやってみます。

gist.github.com

やっている事は、subSystemListの中身を書き換えて反映しているだけです。playerLoop.subSystemList[4];でUpdateの処理を取り出して、独自の処理を一番先頭に追加して、戻しています。
下の一覧はUnityが呼ぶイベントで取得したイベントの一覧をまとめたものです。

index タイプ 内容
0 Initialization 「時間」のアップデートやステート?の同期など色々
1 EarlyUpdate 「入力」の更新とか色々
2 FixedUpdate 物理演算系や、FixedUpdateの呼び出し
3 PreUpdate 物理演算の反映?とか、マウス入力云々
4 Update アップデート処理
5 PreLateUpdate アニメーションの更新とか色々。最後にLateUpdate
6 PostLateUpdate 布の更新とかレンダリングとか色々(たぶん

 

 結果は下のように、FixedUpdateとUpdateの間に独自のイベントが挟めました。

f:id:tsubaki_t1:20180418004119j:plain

 
特定のイベントを無効にする

最後に特定のイベントを無効にしてみます。
これも単純で、PlayerLoopの中から特定のイベントを取り除いたバージョンを用意して、PlayerLoop.SetPlayerLoopするだけです。

今回はFixedUpdate.PhysicsFixedUpdateを無効にしてみます

gist.github.com

f:id:tsubaki_t1:20180418011522g:plain

一度取り除いたシステムを戻す場合、これが地味に面倒でした。もっと違うやり方もありそうですが、とりあえず動いたのでココにメモしておきます。

 

便利かもしれないが、利用は用法用量を守って

今回紹介した「毎フレーム処理の上書き」「差し込み」「停止」以外にも、「順番の差し替え」等が可能みたいです。
ただし、メインループを「上書き」や「差し替え」「停止」した場合、Unity自体が想定通りに動かなくなる可能性が割と高いので、使う際にはよく考えて使う必要があります。

ローレベルな機能なので、使い方には十分注意したほうが良さそうです
個人的には、Update前に初期化処理とかを追加したり、フレームの最後に情報を収集したりするのが良い使い方かなという認識です。

 

関連

下の記事で紹介されてる機能を参考にしました。

www.patreon.comRuntimeInitializeOnLoadMethodについての記事

tsubakit1.hateblo.jp実行順の制御

tsubakit1.hateblo.jp