(:3[kanのメモ帳]

個人ゲーム開発者kan.kikuchiのメモ的技術ブログ。月木更新でUnity関連がメイン。

メニューを追加するための属性「MenuItem」は意外と多機能【Unity】【エディタ拡張】【属性】


このエントリーをはてなブックマークに追加


この記事でのバージョン
Unity 2018.2.14f1


はじめに

Unityのエディタ拡張にはMenuItemというメニューを追加したい時に使える属性があります。

このMenuItemは簡単かつ便利なので、知っていおいた方が良いですし、

エディタ拡張の入門としてもオススメです。

ただ、意外と多機能なのでMenuItem自体は使っていても、知らない使い方があるかもしれません。

[MenuItem("自作メニュー/MenuItemは/意外と/多機能")]
private static void NoMeaning() { }

[MenuItem("自作メニュー/Shortcut #&g+")]
private static void Shortcut() { }

[MenuItem("自作メニュー/CheckMark")]
private static void CheckMark() {
  string menuPath = "自作メニュー/CheckMark";
  bool isChecked = Menu.GetChecked(menuPath);
  Menu.SetChecked(menuPath, !isChecked);
}

[MenuItem("自作メニュー/Validate")]
private static void Validate() { }

[MenuItem("自作メニュー/Validate", validate = true)]
private static bool ValidateValidation() {
  return Application.isPlaying;
}

[MenuItem("自作メニュー/Priority1", priority = 1)]
private static void Priority1() { }

[MenuItem("自作メニュー/Priority100", priority = 100)]
private static void Priority100() { }

f:id:kan_kikuchi:20181125124142j:plain


という事で今回はこのMenuItemについてまとめてみました!

なお、他にもこんなことが出来るみたいなのがあれば、ご一報頂けると幸いです。


基本的な使い方

MenuItemは最初にも述べた通り、Unityエディタにメニューを追加するためのものです。

使い方は簡単、staticなメソッド(privateでもOK)にMenuItemという属性を付けるだけ。

この時、引数にメニューを表示する場所を指定します。

ただし、"ShowLog"のように直で指定する事は出来ず、必ず"Tools/ShowLog"のように階層を付けます。

using UnityEngine;
using UnityEditor;

public class MenuItemSample{

  /// <summary>
  /// ログを表示する
  /// </summary>
  [MenuItem("Tools/ShowLog")]
  private static void ShowLog() {
    Debug.Log("ログ");
  }

}

f:id:kan_kikuchi:20181118115501j:plain


このメニューは、Windowなどの最初からあるメニューの下に追加する事も可能ですし、

 [MenuItem("Window/ShowLog")]

f:id:kan_kikuchi:20181118115554j:plain


メニュー名を日本語にしたり、階層を増やすことも可能です。

[MenuItem("宝箱/取る/本当に?/絶対?/どうしても?")]
private static void AAA() {}

[MenuItem("宝箱/取らない")]
private static void BBB() {}

f:id:kan_kikuchi:20181118120207j:plain


ショートカットキー(ホットキー)の設定

以前、記事にもしましたが、MenuItemはショートカットキー(ホットキー)を設定する事も可能です。


使い方は以下のように、

引数のメニューを表示する場所の後にスペースを空けてキーを追加するだけです。

/// <summary>
/// ログを表示する(Shift + Alt + gキーを押すと実行される)
/// </summary>
[MenuItem("Tools/ShowLog #&g+")]
private static void ShowLog() {
  Debug.Log("ログ");
}

f:id:kan_kikuchi:20181121083919j:plain


また、アルファベット以外に設定可能なキーは以下の通りです。

  • % : CTRL (Windows) または command (OSX)
  • # : Shift
  • & : Altキー
  • LEFT/RIGHT/UP/DOWN : 方向キー
  • F1…F2 : Fキー
  • HOME, END, PGUP, PGDN : HOME、END, PageUp, PageDown


ただし、一つのキー単独で指定する場合はアンダーバー(_)をキーの前に置く必要があります。

/// <summary>
/// ログを表示する(gキーを押すと実行される)
/// </summary>
[MenuItem("Tools/ShowLog _g")]
private static void ShowLog() {
  Debug.Log("ログ");
}

f:id:kan_kikuchi:20181121084833j:plain


なお、デフォルトのショートカットキーと同じものを設定してしまうと、

デフォルトの方が優先されてしまいます。


ちなみにUnity 2019.1からは新システムShortcut Managerがあるので、

ショートカットが重複してると警告を出してくれますし、

Shortcut Managerからショートカットの設定や、デフォルトの変更をする事も出来ます。





メニューの追加場所を変える

通常、MenuItemは画面上部に表示されますが、


f:id:kan_kikuchi:20181121085637j:plain


特殊なパスを使えば別の所に追加する事も可能です。

その特殊なパスというのが以下の3つになります。


ProjectビューのCreate

パスの最初を"Assets/Create/"にするとProjectビューのCreateにメニューを追加出来ます。

[MenuItem("Assets/Create/ShowLog")]
private static void ShowLog() {
  Debug.Log("ログ");
}

f:id:kan_kikuchi:20181121140844j:plain


こちらも、上部に通常通り表示されます。


f:id:kan_kikuchi:20181121140855j:plain


Projectビュー上で右クリックした所

パスの最初を"Assets/"にするとProjectビュー上で右クリックした所にメニューを追加出来ます。

[MenuItem("Assets/ShowLog")]
private static void ShowLog() {
  Debug.Log("ログ");
}

f:id:kan_kikuchi:20181121140252j:plain


なお、上部にも通常通り表示されます。


f:id:kan_kikuchi:20181121140315j:plain


コンポーネントの歯車

パスの最初を"CONTEXT/コンポーネント名"にするとコンポーネントの歯車にメニューを追加出来ます。

例えばRigidBodyの場合だと以下のような感じ。

[MenuItem("CONTEXT/Rigidbody/ShowLog")]
private static void ShowLog() {
  Debug.Log("ログ");
}

f:id:kan_kikuchi:20181121141341j:plain


なお、引数にMenuCommandを付けると、実行したコンポーネントを取得する事が可能です。

[MenuItem("CONTEXT/Rigidbody/ShowLog")]
private static void ShowLog(MenuCommand menuCommand) {
  //menuCommandから実行したRigidbodyを取得出来る
  Rigidbody rigidbody = menuCommand.context as Rigidbody;

  //Rigidbodyが付いてるゲームオブジェクトの名前をログで表示
  Debug.Log(rigidbody.gameObject.name);
}

f:id:kan_kikuchi:20181121203639j:plain


もちろん自作のコンポーネントに追加する事も出来ます。

  [MenuItem("CONTEXT/NewBehaviourScript/ShowLog")]
  private static void ShowLog() {
    Debug.Log("ログ");
  }

f:id:kan_kikuchi:20181121141404j:plain


なお、ContextMenuでも同様の事が可能です。

ただし、こちらはRigidbodyなどのデフォルトで用意されているものには使えません。

using UnityEngine;

public class NewBehaviourScript : MonoBehaviour {

  [ContextMenu("ShowLog")]
  private void ShowLog(){
    Debug.Log("ログ");
  }

}


余談ですがContextMenuItemを使えば、変数を右クリックした所にメニューを追加する事も出来ます。

この場合、第一引数に表示名、第二引数に実行するメソッド名を指定します。

using UnityEngine;

public class NewBehaviourScript : MonoBehaviour {

  [ContextMenuItem("Reset", "ResetText")]
  public string Text = "";

  //テキストを空にする
  private void ResetText() {
    Text = "";
  }

}

f:id:kan_kikuchi:20181121210244j:plain


無効にして使えないようにする

MenuItemは同じパスでvalidateという引数をtrueにし、返り値をboolにすれば

そのメニューが使えるかどうかの判定用のメソッドに出来ます。

具体的には以下のような感じです。

[MenuItem("Tools/ShowLog")]
private static void ShowLog() {
  Debug.Log("ログ");
}

/// <summary>
/// ShowLogが使えるか判定する用のメソッド
/// </summary>
[MenuItem("Tools/ShowLog", validate = true)]
private static bool ShowLogValidation() {
  return !Application.isPlaying;//実行中は使えない
}

f:id:kan_kikuchi:20181121142740g:plain


チェックマークを付ける

MenuのSetCheckedを使えば、メニューにチェックを付けたり外したりも出来ます。

同様にMenuのGetCheckedを使えばチェックが付いてるかが分かります。

具体的には以下のような感じです。

//押す度にチェックが切り替わる
[MenuItem("Tools/ChangeState")]
private static void ChangeState() {

  //現在チェックされているかを取得する(デフォルトではチェックされていない)
  string menuPath = "Tools/ChangeState";//MenuItemで指定したパスと同じものを使う
  bool isChecked = Menu.GetChecked(menuPath);

  //逆のを設定する
  Menu.SetChecked(menuPath, !isChecked);
}

f:id:kan_kikuchi:20181121203229j:plain


なお、MenuItemを設定したメソッドと違う場所で使う事も可能です。

また、Unityエディタを再起動するとチェックは外れます。


並びの優先度を指定、区切り線を追加

MenuItemのpriorityという引数を使えば、並びの優先度を指定する事も可能です。

なおpriorityは低いほどメニューは上に表示され、同じ値を設定しても問題ありません。

さらにpriorityが付いていないか、priorityに11以上差をつければ区切り線が表示されます。

具体的には以下のような感じです。


[MenuItem("Tools/ShowLog1", priority = 1)]
private static void ShowLog1() {
  Debug.Log("ログ");
}

[MenuItem("Tools/ShowLog2", priority = 2)]
private static void ShowLog2() {
  Debug.Log("ログ");
}

[MenuItem("Tools/ShowLog50", priority = 50)]
private static void ShowLog50() {
  Debug.Log("ログ");
}

[MenuItem("Tools/ShowLog70", priority = 70)]
private static void ShowLog70() {
  Debug.Log("ログ");
}

f:id:kan_kikuchi:20181121202300j:plain


ただし1番近いやつからの差が11以上の時に区切り線が表示されるので、

後から間の値を設定し、幅が10以下になったら区切りが消えるので注意が必要です。

[MenuItem("Tools/ShowLog1", priority = 1)]
private static void ShowLog1() {
  Debug.Log("ログ");
}

[MenuItem("Tools/ShowLog2", priority = 2)]
private static void ShowLog2() {
  Debug.Log("ログ");
}

[MenuItem("Tools/ShowLog50", priority = 50)]
private static void ShowLog50() {
  Debug.Log("ログ");
}

//50と70の間に60を追加
[MenuItem("Tools/ShowLog60", priority = 60)]
private static void ShowLog60() {
  Debug.Log("ログ");
}

[MenuItem("Tools/ShowLog70", priority = 70)]
private static void ShowLog70() {
  Debug.Log("ログ");
}

f:id:kan_kikuchi:20181121202357j:plain