概要
Windows上の Unity限定の話です。
Unity で開発中、デバッグのために実行を一時停止したいとき、いくつかの方法があります。
- Debug.Break() : ゲームを一時停止した状態でシーン内のオブジェクトの状態を確認できるのでとても便利です。
- 欠点: 瞬時に実行が止まるわけではなく、次のフレームまで実行が進んでから停止します。
- デバッガのブレークポイント : 止めたところで変数の内容をウォッチしたりステップ実行したりできます。
- 欠点: 事前にブレークポイントを置かないといけない。複雑な停止条件をつけたブレークポイントを置いても、うっかり「すべてのブレークポイントの削除」で消してしまう。
x86 CPU 上で C / C++ を使っているなら、
__asm int 3;
というアセンブリ命令 (3番割り込み) によってデバッガのブレークポイントと同じ効果を発生させることができます。これなら即座に実行が止まり、また「すべてのブレークポイントの削除」で消えてしまうこともありません。
このようなことが Unity (Windows) の C# でもできないか?
というのが今回の趣旨です。
結論
以下のように呼び出すと
MyDebug.HardPause("YOU MUST DEBUG THIS CODE");
以下のようなメッセージを表示して実行が停止します (Unity Editor も停止します)。
表示に従って
- デバッガを Unity にアタッチ
- HardPause()内の Thread.Sleep(1000); の行にブレークポイントを置く
- OKボタンを押してメッセージボックスを閉じる
- デバッガがそのブレークポイントで止まる
- デバッガを使って HardPause()内の無限ループを抜ける
という操作が必要になりますが、 C# スクリプトから強制的に即座に一時停止することができます。(操作が面倒臭いのは認める)
止めるためのコード
MyDebug
using System.Threading;
using UnityEngine;
class MyDebug
{
// Windows API の MessageBox を呼べるようにする。
[DllImport("User32.dll", CharSet = CharSet.Unicode)]
public static extern int MessageBox(IntPtr h, string m, string c, int type);
static void HardPause(string message)
{
string fullMessage = $"Attach the debugger to Unity,\n"
+ "set breakpoint at Thread.Sleep(1000); in HardPause(),\n"
+ "and press OK button of this message box,\n"
+ "Then set the variable loop=false; in the debugger\n"
+ "to continue.\n\n"
+ message;
MessageBox((IntPtr)0, fullMessage, "Hard paused for debug!", 0);
Debug.Log(message);
bool loop = true;
while (loop)
{
Thread.Sleep(1000);
}
Debug.Log($"HardPause() - exit the loop.");
}
}
要点
- Thread.Sleep() で無限ループするだけでは、ただ Unity Editor が反応しなくなるだけで、何が起きたのか分からない。
- Console も動かなくなるので Debug.Log() でメッセージを出すことはできない。
- Windows API の MessageBox() を使って Unity の外でメッセージを表示する。