關於 web service, unity, blogger 等軟體工程筆記

Unity Editor: Menu items

Edit icon 沒有留言

經常自建 Unity 選單 (Menu items) 來執行專案客製化的功能,紀錄在 Unity5 中如何建立選單以及各式自建選單的使用範例。

Menu Items

Menu item

自定選單通常使用 UnityEditor.MenuItem 來建立:

using UnityEditor;
using UnityEngine;
public class Example {
   [MenuItem ("Example/Do Something")]
   static void DoSomething () {
      Debug.Log ("Doing Something...");
   }
}

由於引用 UnityEditor,為了能夠建置專案成功,通常會將 Example.cs 放置於 Editor 資料夾或其子資料夾內,確保該 script 不會遊戲發佈時建置,進而導致 CS0246 錯誤:

error CS0246: The type or namespace name `UnityEditor' could not be found. Are you missing an assembly reference?

或者使用 #if UNITY_EDITOR 定義來修正建置時 CS0246 的錯誤:

#if UNITY_EDITOR
using UnityEditor;
using UnityEngine;
public class Example {
   [MenuItem ("Example/Do Something")]
   static void DoSomething () {
      Debug.Log ("Doing Something...");
   }
}
#endif

Hotkeys (Keystrokes)

Hotkeys

可自訂選單熱鍵,透過幾個符號的組合來達成:

  • %,ctrl on Windows,cmd on macOS
  • #, shift
  • &,alt
  • _,如果不使用以上修飾鍵
  • #KEY,特殊鍵
    • #LEFT,#RIGHT,#UP,#DOWN,方向左右上下按鍵
    • #F1, #F2, ... , #F12
    • #HOME, #END, #PGUP, #PGDN
// 按下 G
[MenuItem ("Example/Do1 _g")]

// 按下 Ctrl + G
[MenuItem ("Example/Do2 %g")]

// 按下 Shift + Ctrl + G
[MenuItem ("Example/Do3 #%g")]

// 按下 Shift + Ctrl + Alt + G
[MenuItem ("Example/Do4 #%&g")]

Validation

Validation

Menu item 可以加入驗證 (validate function),只有當條件判斷滿足時,使用者才可以操作點擊該選單:

// 只有當編輯器是 Play Mode 時,才可以操作 Example/Do Something
using UnityEditor;
public class Example
{
   [MenuItem ("Example/Do Something")]
   static void DoSomething () {
      Debug.Log ("Doing Something...");
   }

   [MenuItem("Example/Do Something", true)]
   static bool DoSomethingValidate()
   {
      return EditorApplication.isPlaying;
   }
}

Item Priority

Priority

若沒有特別宣告順序優先度的 menuItem,通常會根據其函數宣告順序,擺在該選單的最底下。但可以自訂優先度,確保 menuItem 順序:

using UnityEditor;
using UnityEngine;
public class Example
{
   [MenuItem("Example/Do Something", false, 10)]
   static void DoSomething()
   {
      Debug.Log("Doing Something...");
   }
   [MenuItem("Example/Do Something2", false, 8)]
   static void DoSomething2()
   {
      Debug.Log("Doing Something2...");
   }
   [MenuItem("Example/Do Something3", false, 5)]
   static void DoSomething3()
   {
      Debug.Log("Doing Something3...");
   }
}

注意 MenuItem 的宣告,可以使用其建構子設定所有參數,或者用 C# 5.0 新語法 (syntax) 設定其參數:

// 原本範例
[MenuItem("Example/Do Something", false, 10)]

// 可以改成 (validate 預設值為 false)
[MenuItem("Example/Do Something", priority = 10)]

// 或者是
[MenuItem("Example/Do Something", priority = 10, validate = false)]

其 MenuItem 定義:

namespace UnityEditor
{
   public sealed class MenuItem : Attribute
   {
      public string menuItem;
      public int priority;
      public bool validate;

      public MenuItem(string itemName);
      public MenuItem(string itemName, bool isValidateFunction);
      public MenuItem(string itemName, bool isValidateFunction, int priority);
   }
}

Sepetatror

Sepetatror

若要增加選單中增加分隔線 (sepetatror),如上圖所示,只要兩個 menu items 其優先度差超過 100 以上,這兩個 menu items 中間便自動產生分隔線,例如以下範例:

using UnityEditor;
using UnityEngine;
public class Example
{
   [MenuItem("Example/Do Something", false, 0)]
   static void DoSomething()
   {
      Debug.Log("Doing Something...");
   }
   [MenuItem("Example/Do Something2", false, 1)]
   static void DoSomething2()
   {
      Debug.Log("Doing Something2...");
   }
   [MenuItem("Example/Do Something3", false, 300)]
   static void DoSomething3()
   {
      Debug.Log("Doing Something3...");
   }
}

Special Paths

  • Assets/,除了在上方功能表會出現以外,也能在 Project view 的功能選單 (context menu) 出現

    using UnityEditor;
    public class Example
    {
       [MenuItem("Assets/Do Something")]
       static void DoSomething()
       {
       }
    }
    
  • Assets/Create/,建立自訂 assets,詳見 Create Assets

  • CONTEXT/ComponentName,在 inspector 該組件編輯頁中,滑鼠右鍵的功能列表中新增選項,詳見 Context Menu

  • GameObject/,這算是小花招,若想要在 Hierarchy view 中,滑鼠右鍵功能選單出現自訂選項的話,設定路徑為 GameObject 且 priority 在 11-49 之間,便可達到此功能,如以下例子:

    using UnityEditor;
    public class Example
    {
       [MenuItem("GameObject/Toggle Active", priority = 11)]
       static void ToggleActive()
       {
          var go = Selection.activeGameObject;
          go.SetActive(!go.activeSelf);
       }
    
       [MenuItem("GameObject/Toggle Active", true)]
       static bool ToggleActiveValidate()
       {
          return Selection.activeGameObject != null;
       }
    }
    

Add Compoments

Add component

自定義 Component/,來建立特別組件的新增:

using UnityEngine;
public class Example : MonoBehaviour
{
#if UNITY_EDITOR
   [UnityEditor.MenuItem("Component/Example")]
   static void AddComponent()
   {
      UnityEditor.Selection.activeGameObject.AddComponent();
   }
   [UnityEditor.MenuItem("Component/Example", true)]
   static bool AddComponentValidate()
   {
      return UnityEditor.Selection.activeGameObject != null;
   }
#endif
}

或是使用已經定義好的 attribute 達到相同功能:

using UnityEngine;
[AddComponentMenu("Example")]
public class Example : MonoBehaviour
{
}

Create Assets

Create asset

自定 Assets/Create/Somthing,來建立此專案定義的自訂型態的 assets。例如定義 Example scriptable object,並且設定 menu item 去建立 assets:

using System.IO;
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
#endif
public class Example : ScriptableObject
{
#if UNITY_EDITOR
   [MenuItem("Assets/Create/Example")]
   static void DoSomething()
   {
      var asset = ScriptableObject.CreateInstance();
      var path = AssetDatabase.GetAssetPath(Selection.activeObject);
      if (string.IsNullOrEmpty(path))
      {
         path = "Assets";
      }
      else if (Path.GetExtension(path) != "")
      {
         path = path.Replace(Path.GetFileName(AssetDatabase.GetAssetPath(Selection.activeObject)), string.Empty);
      }
      var assetPathAndName = AssetDatabase.GenerateUniqueAssetPath(path + "/New " + asset.GetType().ToString() + ".asset");
      AssetDatabase.CreateAsset(asset, assetPathAndName);
      AssetDatabase.SaveAssets();
      AssetDatabase.Refresh();
      EditorUtility.FocusProjectWindow();
      Selection.activeObject = asset;
   }
#endif
}

或是直接使用 attribute 定義達到相同的功能:

using UnityEngine;
[CreateAssetMenu]
public partail class Example : ScriptableObject
{
}

Context Menu

Context menu

在指定組件 (component) 功能選單 (context menu) 加入選項,讓使用者能在 inspector 中,在該組件編輯視窗中,直接按下滑鼠右鍵,能夠點選該選項操作:

using UnityEditor;
using UnityEngine;
public class Example
{
   [MenuItem("CONTEXT/Transform/Set Identity")]
   static void SetIdentity(MenuCommand command)
   {
      var transform = (command.context as Transform);
      transform.localPosition = Vector3.zero;
      transform.localRotation = Quaternion.identity;
      transform.localScale = Vector3.one;
   }
}

或是使用已經定義好的 attribute 對自建組件達到相同功能,必須是 non-static function,但此方式沒辦法為其他組件擴充:

using UnityEngine;
class Example : MonoBehaviour
{
   [SerializeField]
   int number;
   [ContextMenu("Set Random Number")]
   void SetRandomNumber()
   {
      this.number = Random.Range(int.MinValue, int.MaxValue);
   }
}
Context menu

亦或是針對欄位建立選單:

using UnityEngine;
public class Example : MonoBehaviour
{
   [ContextMenuItem("Set infinity", "SetInfinity")]
   [SerializeField]
   float number;
   void SetInfinity()
   {
      this.number = float.PositiveInfinity;
   }
}
Context menu for property

Checkbox

Checkbox

在選單上設定核取方框 (Check box),讓使用者知道目前是什麼設定,使用 Menu.SetChecked 設定,亦可用 Menu.GetChecked 來取得是否選取,例如自訂語言的例子:

using UnityEditor;

public enum Language
{
   English,
   TraditionalChinese,
   Japanese,
}

public class LanguageMenuItems
{
   const string MenuItemEnglish = "Lanuage/English";
   const string MenuItemTraditionalChinese = "Lanuage/Traditional Chinese";
   const string MenuItemJapanese = "Lanuage/Japanese";

   public static Language Current
   {
      get;
      set;
   }

   [MenuItem(MenuItemEnglish)]
   static void SetEnglish()
   {
      Current = Language.English;
   }

   [MenuItem(MenuItemEnglish, true)]
   static bool SetEnglishValidate()
   {
      Menu.SetChecked(MenuItemEnglish, Current == Language.English);
      return true;
   }

   [MenuItem(MenuItemTraditionalChinese)]
   static void SetTraditionalChinese()
   {
      Current = Language.TraditionalChinese;
   }

   [MenuItem(MenuItemTraditionalChinese, true)]
   static bool SetTraditionalChineseValidate()
   {
      Menu.SetChecked(MenuItemTraditionalChinese, Current == Language.TraditionalChinese);
      return true;
   }

   [MenuItem(MenuItemJapanese)]
   static void SetJapanese()
   {
      Current = Language.Japanese;
   }

   [MenuItem(MenuItemJapanese, true)]
   static bool SetJapaneseValidate()
   {
      Menu.SetChecked(MenuItemJapanese, Current == Language.Japanese);
      return true;
   }
}

Others

  • UnityEditor.EditorApplication.ExecuteMenuItem,執行指定 menu item 的功能,例如以下呼叫 menu item 來建立一個空 GameObject 的範例:

    UnityEditor.EditorApplication.ExecuteMenuItem("GameObject/Create Empty");
    
  • UnityEditor.GenericMenu,客製化自己的選單,請參見 ScriptReference

Reference

沒有留言: