【Unityエディタ拡張(IMGUI)】GenericMenuからPopupWindowを開く際の注意点

GenericMenuでPopupWindow.Showを呼び出す際にいろいろ注意点があったのでメモとして残しておきます。

要件

  • 右クリックして表示されるメニューの「設定」を押したら設定ウィンドウをポップアップ表示したい。
  • 設定ウィンドウの表示位置は右クリックした位置に表示したい。
  • ポップアップ表示を呼び出すコードがどこであろうと位置ズレせずに表示させたい。
    • OnGUI直下だけとは限らない。
    • もしかしたらGUILayout.Windowの中かもしれない。
    • もしかしたらUIElementsでIMGUIContainerを使用しているかもしれない。
      • しかもtransform.position変更して表示位置を変えてるかもしれない。

注意点

GenericMenu経由してPopupWindow.Showを呼び出すとExitGUIExceptionが発生する。

以前不具合化と思いバグレポ送るもWon’t Fixにされてしまいました。

https://issuetracker.unity3d.com/issues/exitguiexception-is-thrown-when-popupwindow-dot-show-is-called-from-genericmenu

なので、ExitGUIExceptionをcatchして握りつぶす必要があります。

ExitGUIExceptionがそのまま投げられるとは限らないので、TargetInvocationExceptionだったらInnerExceptionも確認するようにします。

public static bool ShouldRethrowException(System.Exception exception)
{
	while (exception is TargetInvocationException && exception.InnerException != null)
		exception = exception.InnerException;
	return exception is ExitGUIException;
}

※UnityCsReferenceに掲載されているメソッドをそのまま移植したのでメソッド名は気にしないこと

PopupWindow.Showを呼ぶ側では以下のようなコードでExitGUIExceptionを握りつぶし。

void Popup(Rect position)
{
	try
	{
		UnityEditor.PopupWindow.Show(position, s_Content);
	}
	catch (System.Exception ex)
	{
		if (!ShouldRethrowException(ex))
		{
			throw;
		}
	}
}

※s_ContentはPopupWindowContentから継承したクラスのインスタンス。

右クリックした位置に表示

PopupWindow.ShowはGUI.Buttonなどから呼ばれる想定で作成されているようなので、GenericMenuからそのままEvent.mousePositionの場所に表示しようとするとズレてしまいます。

GenericMenuのコールバックメソッド内と外とではUnity内部で取り扱っている座標系が異なるため座標変換が必要です。

流れとしては以下の通り。

  • EventType.ContextClickが来る。
  • Event.mousePositionを一旦GUIUtility.GUIToScreenPointでスクリーン座標に変換する。
  • GenericMenuのコールバックメソッド内でGUIUtility.ScreenToPointを呼びGUI座標系に戻す。
  • PopupWindow.Showを呼ぶ(内部でGUIUtility.GUIToScreenPointが呼び出され再びスクリーン座標系に変換される)
Event evt = Event.current;
switch (evt.type)
{
	case EventType.ContextClick:
		{
			Vector2 mousePosition = evt.mousePosition;
			Vector2 popupPosition = GUIUtility.GUIToScreenPoint(mousePosition);

			GenericMenu menu = new GenericMenu();

			menu.AddItem(new GUIContent("Open Popup"), false, () =>
			{
				popupPosition = GUIUtility.ScreenToGUIPoint(popupPosition);
				Popup(new Rect(popupPosition, Vector2.zero)); // 上記Popupメソッド
			});

			menu.ShowAsContext();
		}
		break;
}

こんな感じ。

ちなみに

if( GUILayout.Button("Open") )
{
	PopupWindow.Show(GUILayoutUtility.GetLastRect(), s_Content);
}

などなど、GenericMenu経由さえしなければ何も対策は必要ありません。

Unityのアセット販売中!

ステートマシンの状態遷移やパラメータはエディタで編集でき、
ゲームロジックに依存するステートの挙動はスクリプトで記述可能なエディタ拡張。

詳細はこちら

RPGツクールVXやWOLF RPGエディターのオートタイルに準拠したエディタ拡張。

詳細はこちら

オススメ!