はじめまして、すらです。
今回は前々から悩まされていた、テストプレイ中のフリーズ現象の解決記事です。
原因はいろいろあると思いますが、私の環境の場合はPCスペック的にも問題ないですし、各Updateメソッドで変なことをしているわけでもない(つもり)ので原因がわからず、放置していました。
フリーズが起きる頻度も大して高くなかったのでそれでよかったんですが、ついにほぼ100%フリーズが起きるようになってしまい、今回原因、解決策を探した次第です。
原因はEditorLoopのオーバーワーク
Unityには標準機能としてProfilerという機能が用意されています。
こちらは、処理負荷を可視化できるツールです。
ゲームやエディタの内部で、どの処理がどれだけCPUやメモリを消費しているかをリアルタイムに確認でき、
ゲームのカクつきやフリーズの原因特定に非常に役立ちます。
(Profilerの具体的な見かたなどは割愛します)
Profilerによると、EditorLoopがとてつもなく長い時間処理をし続けていて、ほかの処理を止めているということがわかります。
ではこのEditorLoopというのは何者なのか。
EditorLoopは、Unityエディタ上で動いているすべてのエディタ関連処理をまとめて管理しているループ処理です。
テストプレイ中も、ゲームビューだけでなくヒエラルキー、インスペクター、シーンビューといったさまざまなビューの更新を行っています。
つまり、エディタの「裏方スタッフ総まとめ」みたいな存在。
テストプレイ中にいろんなビューを開いたままにすることで、このEditorLoop君が過労になり、
「すまん、ワシもう動けんわ」ってなるわけですね。
解決策は不必要なビューを表示させないこと
テストプレイ中はEditorLoop君の仕事量を減らしてあげましょう。
表示させないことで負荷が減り、UnityEditorがフリーズしなくなります。(少なくとも私の場合はそうでした)
ここでいうビューというのは、ヒエラルキーだったりインスペクターだったりシーンビューなどですね。
しかし問題になるのが、テストプレイ中とそれ以外のときとで、UnityEditorのビューの表示を変える手間が増えるということですよね。
開発スタイル次第だとは思いますが、テストプレイは頻繁に行う作業だと思います。そのたびにビューを表示を変更するのはかなり面倒くさいです。
ここではその面倒くささを解消する2つの方法をご紹介します。
ウィンドウプリセットを使う
標準機能としてウィンドウの状態をプリセットとして保存できる機能があります。
Window > Layouts > Save Layout
こちらを選択することで、現在開いているビューの状態をプリセットととして保存することができます。
保存したプリセットはWindow > Layouts の中に保存されますので、そちらを選ぶとプリセット内容でビューが表示されます。
よって、ここに編集用のプリセット、テストプレイ用のプリセットを用意すれば、簡単に切り替えることができますね。
テストプレイ開始と終了時に自動でプリセットを切り替える
テストプレイのたびにプリセットを切り替えることすらめんどくさい人にはこちらの方法をおすすめします。
ただこちらの場合はスクリプトを書く必要があるので、お手軽ではないですがストレスフリーになります。
といってもほとんどコピペで動くはずなので問題ないと思います。
まず、Assets/EditorフォルダにAutoLayoutSwitcherというスクリプトファイルを作成します。
Editorフォルダがない場合は作成してください。
AutoLayoutSwitcherの中身は以下をコピペ。
using UnityEditor;
using System.Reflection;
using System.IO;
[InitializeOnLoad]
public static class AutoLayoutSwitcher
{
// ★ ここに自動で切り替えたいレイアウトファイル(.wlt)のフルパスを書く
private static readonly string playModeLayoutPath = "C:/Users/【ユーザー名】/AppData/Roaming/Unity/Editor-5.x/Preferences/Layouts/default/TestPlay.wlt";
private static readonly string editModeLayoutPath = "C:/Users/【ユーザー名】/AppData/Roaming/Unity/Editor-5.x/Preferences/Layouts/default/Develop.wlt";
static AutoLayoutSwitcher()
{
EditorApplication.playModeStateChanged += OnPlayModeChanged;
}
private static void OnPlayModeChanged(PlayModeStateChange state)
{
if (state == PlayModeStateChange.EnteredPlayMode)
{
if (File.Exists(playModeLayoutPath))
{
LoadWindowLayout(playModeLayoutPath);
}
else
{
UnityEngine.Debug.LogWarning("指定したレイアウトファイルが見つかりません。");
}
}
else if (state == PlayModeStateChange.EnteredEditMode)
{
if (File.Exists(editModeLayoutPath))
{
LoadWindowLayout(editModeLayoutPath);
}
else
{
UnityEngine.Debug.LogWarning("指定したレイアウトファイルが見つかりません。");
}
}
}
private static void LoadWindowLayout(string path)
{
var windowLayoutType = typeof(EditorApplication).Assembly.GetType("UnityEditor.WindowLayout");
var method = windowLayoutType.GetMethod("LoadWindowLayout", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static, null, new[] { typeof(string), typeof(bool) }, null);
if (method != null)
{
method.Invoke(null, new object[] { path, false });
}
else
{
UnityEngine.Debug.LogError("WindowLayout.LoadWindowLayout メソッドが見つかりません。");
}
}
}
変更する必要があるのはplayModeLayoutPathとeditModeLayoutPathの2か所。
作成したプリセットは.wltという拡張子で特定のフォルダに生成されるので、それを指定してあげる必要があります。
playModeLayoutPathにはテストプレイ時のレイアウトプリセット
editModeLayoutPathにはエディット時のレイアウトプリセット
先ほどの方法であらかじめ作成しておいて、かならず絶対パスで指定してください。
(.wltファイルの場所は上記コードのパスを参考にしてください。)
※AppDataフォルダがない場合は、非表示になっている可能性が高いです。
Windows10ではエクスプローラーの表示タブのなかにある、隠しファイルにチェックを入れると見えるようになると思います。
Windows11のエクスプローラーでも隠しファイルを表示させるようなオプションがどこかにあるはずです。さがしてみてください。(調べるのが一番早いですね)
これで自動でプリセットが切り替わるようになると思います。
…が、おそらくコンソールに
MissingReferenceException: The object of type ‘DockArea’ has been destroyed but you are still trying to access it.
Your script should either check if it is null or you should not destroy the object.
というエラーが出ると思います。
DockAreaというのは、Unityのウィンドウ(タブ)の中身を管理する内部クラスで、
レイアウトプリセットをロードするときに、「今開いてるウィンドウたち(DockArea)」を全部一回破壊して作り直す処理が裏で走ります。
しかしそのタイミングで、Unityの内部コード(例えばGameViewなど)がすでに破壊されたDockAreaにアクセスしようとして起きるエラーです。
レイアウトのロードが終了した際にそちらにアクセスしなおすので、正直放置しても問題ないエラーです。
ただやっぱりエラーが出ているのは気になるので、対処してみます。
private static void OnPlayModeChanged(PlayModeStateChange state)
{
if (state == PlayModeStateChange.EnteredPlayMode)
{
EditorApplication.delayCall += () =>
{
if (File.Exists(playModeLayoutPath))
{
LoadWindowLayout(playModeLayoutPath);
}
else
{
UnityEngine.Debug.LogWarning("指定したレイアウトファイルが見つかりません");
}
};
}
else if (state == PlayModeStateChange.EnteredEditMode)
{
EditorApplication.delayCall += () =>
{
if (File.Exists(editModeLayoutPath))
{
LoadWindowLayout(editModeLayoutPath);
}
else
{
UnityEngine.Debug.LogWarning("指定したレイアウトファイルが見つかりません");
}
};
}
}
OnPlayModeChangedを上記のもので上書きしてください。
これで削除されたDockAreaにアクセスしてしまうことが減ると思います。
まとめ
UnityEditorがフリーズしてしまう原因はEditorLoop君の過労が原因でした。
テストプレイ中は最低限のビューだけを表示するようにして、少しでもEditorLoop君を楽にしてあげましょう。
表示を切り替えるのが面倒なひと向けに自動で切り替える方法も紹介しました。
ほぼコピペで動くので難しそうと思わずやってみてください。開発が捗りますよ!
追記
レイアウトを切り替えるとヒエラルキーのオブジェクトの展開状況がリセットされるようです。
こちらはどうしようもなさそうで、いちいち再展開するのも面倒くさいので…プリセットの自動切り換えも…結局OFFにしてしまいました。
以前はゲームビューとシーンビューを並べてテストプレイしていましたが、とりあえずはシーンビューとゲームビューを並べないようにすることでフリーズは予防できています。
なのでプリセットでの対応は本当に最終手段かなと…。
ちなみにAutoLayoutSwitcherをOFFにしたい場合は、ファイルの中身全部コメントアウトでもいいですし、エクスプローラー上から拡張子を変えてしまうのでもOKです。
「AutoLayoutSwitcher.cs.disabled」のようにすれば、それだけでUnityは無視してくれますし、元に戻すのも簡単です。
ただし、UnityEditor上からはできないので注意。あとUnity再起動も必要。