はじめに
今回はPlayerLoop
について紹介し、実際に独自の処理を追加してみたいと思います。
PlayerLoopとは
PlayerLoop
について公式ドキュメントでは以下のように記載されています。
This class contains functions for interacting with the player loop in the core of Unity. You can use this class to get the update order of all native systems and set a custom order with new script entry points inserted.
LowLevel.PlayerLoop - Unity スクリプトリファレンス
説明がPlayerLoop
のクラスの説明なので分かりずらいですが、重要な箇所だけざっと日本語訳すると
PlayerLoopクラスを使うことで、ネイティブシステムの更新順番を取得できたり、新しいスクリプトのエントリーポイントを挿入して独自の順序にすることができます。
つまりはUpdate
といった毎フレーム実行される処理を好きなようにいじることができるという意味です。
PlayerLoopの細かいイベントたちはUniTask
の作者の方がGitHubに挙げてくれていたので貼っておきます。(一部UniTask
のイベントが入っていますが今回は無視してください)
またUnityのバージョンで違いがあるよう(具体的にはUNITY_2020_2_OR_NEWER
)なので、自身のPlyerLoop
を知りたい場合は以下のメソッドを使うことで分かります。
// 現在のPlayerLoopを調べる public static LowLevel.PlayerLoopSystem GetCurrentPlayerLoop (); // デフォルトのPlayerLoopを調べる public static LowLevel.PlayerLoopSystem GetDefaultPlayerLoop ();
LowLevel.PlayerLoop-GetCurrentPlayerLoop - Unity スクリプトリファレンス
PlayerLoopSystem
PlayerLoop
を扱う上でPlayerLoopSystem
構造体は切っても切り離せません。
LowLevel.PlayerLoopSystem - Unity スクリプトリファレンス
PlayerLoopSystem
には5つの変数を保持しますが、その中でも重要な3つを抜粋しました。
名前 | 型 | 意味 |
---|---|---|
subSystemList | PlayerLoopSystem[] | PlayerLoopで一緒に実行されるSubSystemのリスト |
type | Type | ネイティブシステムでの識別子として用いる |
updateDelegate | UpdateFunction | 追加するデリゲート |
LowLevel.PlayerLoopSystem - Unity スクリプトリファレンス
日本語訳がかなり怪しいですが雰囲気が伝われば幸いです。
このsubSystemList
が結構重要な変数で、PlayerLoopSystem
が変数としてPlayerLoopSystem
の配列を保持しています。
これはいわゆる木構造を表現していて、冒頭で書いたデフォルトのPlayerLoopを表現すると以下のようになります。
これらすべてのノード(木の各節点)にupdateDelegate
という実行するメソッドが設定でき、以下の順番で実行されていきます。
また木のルートにあるupdateDelegate
は実行されないことに注意してください。
PlayerLoopの中身自体を書き換える
デフォルトのPlayerLoopに変更を加えるためにはSetPlayerLoop
というメソッドを利用します。
public static void SetPlayerLoop (LowLevel.PlayerLoopSystem loop);
LowLevel.PlayerLoop-SetPlayerLoop - Unity スクリプトリファレンス
独自の処理を追加する前に、PlayerLoop
の中身を丸ごと書き換えてみましょう。
using UnityEngine; using UnityEngine.LowLevel; public class PlayerLoopTest { public struct MyUpdate { } [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)] private static void Init() { var mySystem = new PlayerLoopSystem { subSystemList = new PlayerLoopSystem[] { new PlayerLoopSystem { updateDelegate = CustomUpdate, type = typeof(MyUpdate), } } }; PlayerLoop.SetPlayerLoop(mySystem); } private static void CustomUpdate() { Debug.Log("my update."); } }
ゲームビューにあるCube
にはRigidbody
がアタッチされているのですが、うんともすんとも動きません。不思議な世界です。
またsubSystemList = new PlayerLoopSystem[]
のように書いているのがミソで、以下のように書くと動作しませんでした。(テラシュールブログさんは以下のコードでミスってたみたいです)
var mySystem = new PlayerLoopSystem { type = typeof(MyUpdate), updateDelegate = CustomUpdate, };
途中でも紹介しましたが、木のルートにupdateDelegate
を指定しても動作しません。
ちなみに変化球ですが、以下のようにしても動作しました。
var mySystem = new PlayerLoopSystem { subSystemList = new PlayerLoopSystem[] { new PlayerLoopSystem { subSystemList = new PlayerLoopSystem[] { new PlayerLoopSystem { subSystemList = new PlayerLoopSystem[] { new PlayerLoopSystem { type = typeof(MyUpdate), updateDelegate = CustomUpdate, } } } } } } };
独自の処理を追加する
実際にUpdate
前に独自のイベント関数を追加してみたいと思います。
public class PlayerLoopTest { public struct MyUpdate { } [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)] private static void Init() { var mySystem = new PlayerLoopSystem { type = typeof(MyUpdate), updateDelegate = CustomUpdate, }; var playerloop = PlayerLoop.GetDefaultPlayerLoop(); for (var i = 0; i < playerloop.subSystemList.Length; i++) { if (playerloop.subSystemList[i].type == typeof(Update)) { playerloop.subSystemList[i] = new PlayerLoopSystem { type = playerloop.subSystemList[i].type, updateDelegate = playerloop.subSystemList[i].updateDelegate, subSystemList = playerloop.subSystemList[i].subSystemList.Prepend(mySystem).ToArray(), // subSystemListの先頭にmySystem追加 updateFunction = playerloop.subSystemList[i].updateFunction, loopConditionFunction = playerloop.subSystemList[i].loopConditionFunction, }; break; } } PlayerLoop.SetPlayerLoop(playerloop); } private static void CustomUpdate() { Debug.Log("my update."); } }
こうすると毎回デバッグが出力されながらも、ちゃんとCubeが落下していきました。
さいごに
PlayerLoopですが、PlayerLoopSystem
のデフォルトの木構造さえ理解できればすごい難しいというわけではない気がします。
ただPlayerLoopSystem.type
がいまいちつかめていなく、struct
(class
でもいけた)の型を入れる動作原理を理解できていません。
公式ドキュメントには以下のように書かれていました。
This property is used to identify which native system this belongs to, or to get the name of the managed system to show in the profiler.
ネイティブシステムでの識別子として用いられていて、プロファイラーで見れるそうです。
確かにプロファイラにMyUpdate
という型の名前が使われていました。
ただわざわざ型でなくともstring
でもいいんじゃないのかなとも思わなくないですが、コンパイルとかの制約とかの影響なんですかね?
何か知ってる方がいましたら、コメント等で教えてくださると嬉しいです。
ではまた。