Unityでツールを作るときに個人的に気をつけていること。またはEditorUtilityについて。
Unityでツールを実装するとき、個人的に気をつけていることと、それを実現するためのAPIについて紹介します。
使いやすいツールとは
Unityにおいてツールを作る目的は、主にゲームを作るときの効率化であることが多いと思います。
ツールはプログラマだけではなく、デザイナーさんやプランナーさんなど、様々な方が使うことが想定されます。 そのためプログラマ以外の方たちにも使いやすいツールであることが望ましいです。
ツールを実行している際に、何を実行しているかわからなかったり、その状態で長い間なにも反応がないと、 使い手は「正常にツールが実行されているのだろうか?」と不安になると思います。
そこでツールを実装する際に、下記のことを気をつけています。
- 何をやってるかがわかる
- あとどのぐらいで終わるかがわかる
- なにが起こったかがわかる
要には不安を解消できるよう、適切に情報を提供するように心がけています。
EditorUtilityを用いる
上記を解消するのに EditorUtility
というAPIが便利です。
進捗を表示する
「一括でデータを変換したい」または「重たい処理が処理が複数からなる」場合、ユーザーはその処理がどれだけ時間が必要なのか知りたくなります。 その場合はプログレスバーなので進捗を表示するのが親切です。
EditorUtility
のDisplayProgressBar
というメソッドを用いると簡単にプログレスバーを表示できます。
var total = 1000000;
for (var i = 0; i < total; ++i)
{
// プログレスバー描画
EditorUtility.DisplayProgressBar(
"テストツール",
$"重たい処理の{(i + 1)}番目を実行しています",
(float)i / (float)total);
// ここで処理を行う
DoProcess(i);
}
// プログレスバーのクリア
EditorUtility.ClearProgressBar();
上記の実装で下図のようなプログレスバーが表示されます。
DisplayProgressBar
の第1引数にウインドウのタイトルを、第2引数にメッセージを表示します。第3引数には進捗を0.0 ~ 1.0で渡します。だいたい、処理済みのタスク数 / タスク総数
を渡せばOKです。
ただし1つ注意点として、処理の最後にEditorUtility.ClearProgressBarを必ず呼ぶ必要があります。呼ばないとツールが終了した後もプログレスバーが残り続け、エディタのUIをロックしてしまいます。
そこで処理の途中で例外が発生しても確実にClearProgressBar
を呼べるように、処理全体をtry
句で囲った上で、finally
句の中でこのメソッドを呼ぶと楽です。
try
{
var total = 1000000;
for (var i = 0; i < total; ++i)
{
// プログレスバー描画
EditorUtility.DisplayProgressBar(
"テストツール",
$"重たい処理の{(i + 1)}番目を実行しています",
(float)i / (float)total);
// ここで処理を行う
DoProcess(i);
}
}
catch (System.Exception e)
{
UnityEngine.Debug.LogException(e);
}
finally
{
// DoProcessメソッド内で例外などを吐いても
// Unityが落ちない限りはこの処理を通る
// 確実にプログレスバーをクリアするようにする
EditorUtility.ClearProgressBar();
}
また、途中で処理を中断することをサポートできる場合は、EditorUtility.DisplayCancelableProgressBarを利用するとより親切です。
try
{
var total = 10000000;
for (var i = 0; i < total; ++i)
{
// プログレスバー描画
if (EditorUtility.DisplayCancelableProgressBar(
"テストツール",
$"重たい処理の{(i + 1)}番目を実行しています",
(float)i / (float)total))
{
// 処理が途中でキャンセルされるとこちらにはいる
break;
}
// ここで処理を行う
DoProcess(i);
}
}
catch (System.Exception e)
{
UnityEngine.Debug.LogException(e);
}
finally
{
// 確実にプログレスバーをクリアするようにする
EditorUtility.ClearProgressBar();
}
ダイアログを表示する
処理が終わった後そのままツールを終了すると、ユーザーはそれが正常に終わったのか、それともどこか途中で処理が失敗したのかわかりません。
Debug.Log
でコンソールにログを出力するでも良いのですが、他のログによって該当ログが発見しづらかったり、
そもそも、コンソールログがプログラマ以外にあまり馴染みがなかったりと、ツールを使うユーザーへの通知としては不適切に感じています。
(もちろんエラーログを吐いておくことで、何かエラーが発生した際にエディターログを送ってもらうことで、トラブルシューティングに役立ちます。)
ユーザーへの通知を行うときにはダイアログを表示するのが便利です。EditorUtility
にはEditorUtility.DisplayDialogというメソッドが用意されています。
例えば先程のツールでは下記のようにダイアログを表示すると、ユーザーにとってツールの状態がわかりやすくなるかと思います。
try
{
var total = 10000000;
for (var i = 0; i < total; ++i)
{
// プログレスバー描画
if (EditorUtility.DisplayCancelableProgressBar(
"テストツール",
$"重たい処理の{(i + 1)}番目を実行しています",
(float)i / (float)total))
{
// 処理が途中でキャンセルされるとこちらにはいる
break;
}
// ここで処理を行う
DoProcess(i);
}
EditorUtility.DisplayDialog(
"テストツール",
"処理がすべて正常に終わりました。",
"OK");
}
catch (TestToolException knownException)
{
// DoProcess中のエラー処理のうち、
// その後処理を継続できない場合は
// ユーザー定義の例外を吐いてそれをキャッチするとエラー処理が楽。
EditorUtility.DisplayDialog(
"テストツール",
$"エラーが発生しました。{knownException.Message}",
"OK");
}
catch (System.Exception exception)
{
// それ以外の予期しない例外の場合は、
// 素直にコンソールログに情報を吐き出しておき
// トラブルシューティングのためにプログラマに
// 問い合わせてくれというメッセージを書いておくと良いかも
UnityEngine.Debug.LogException(e);
EditorUtility.DisplayDialog(
"テストツール",
"予期しないエラーが発生しました。",
"OK");
}
finally
{
// 確実にプログレスバーをクリアするようにする
EditorUtility.ClearProgressBar();
}
1点注意点としてDisplayDialog
はそれを呼び出した時点でその行で停止し、
ユーザーがダイアログに反応しないと次の処理を実行しません。
一括で処理を行うようなツールの場合に途中でDisplayDialog
を呼び出すと処理を止めてしまうことに注意しましょう。
その場合は、結果をとりあえずリスト溜め込んでおき、すべての処理が終了後に結果を確認できるウインドウを表示すると親切です。
まとめ
個人的にツール作りにおいて、特にプログラマ以外のことが利用すること想定して大事にしていることと、
それを実現するために便利なEditorUtility
のAPIを紹介しました。
余談ですがEditorUtility
には他にも、ファイル読み込みや保存の際に、
そのファイルパスを選択させるEditorUtility.OpenFilePanelや
EditorUtility.SaveFilePanelなど便利なAPIがたくさんあります。
ツール実装を行う際に、一度API一覧を軽く目を通すと良いかもしれません。