📏

Unity(Editor)/ MenuItemの一覧を取得する

2023/06/04に公開

先日、ドラッグアンドドロップでファイル作成できるユーティリティを作っていた際、
"Assets/Create"の一覧とか取得できないかなと思って調べたんですが、探した限りpublicなものは見つからず…
GitHubの検索機能を使ってUnityCsReferenceを探してみると
Menuクラスにinternalとして「GetMenuItems」という関数を見つけました。[1]
メニューのパスからその中にある全部のMenuItemを一覧で取得できます。
最悪なことに、構造体がinternalなのでめんどくさい!

コード

using System;
using System.Linq;
using System.Collections.Generic;
using System.Reflection;
using UnityEngine;
using UnityEditor;

static class MenuItemInternals
{
   public struct MenuItemInfo
   {
      public string Path { get; set; }
      public bool IsSeparator { get; set; }
      public int Priority { get; set; }
   }
   public static IEnumerable<MenuItemInfo> GetMenuItems(string menuPath, bool includeSeparators, bool localized)
   {
      if (s_GetMenuItems?.Invoke(null, new object[] { menuPath, includeSeparators, localized })
         is not Array result)
      {
         Debug.LogError("(Editor) It could not get menu items. Please check the Unity version!");
         return Enumerable.Empty<MenuItemInfo>();
      }

      return result.Cast<object>().Select(e =>
      {
         return new MenuItemInfo
         {
            Path = s_ScriptingMenuItem_path?.GetValue(e) as string,
            IsSeparator = s_ScriptingMenuItem_isSeparator?.GetValue(e) as bool? ?? false,
            Priority = s_ScriptingMenuItem_priority?.GetValue(e) as int? ?? 0
         };
      });
   }

   private readonly static MethodInfo s_GetMenuItems;
   private readonly static PropertyInfo s_ScriptingMenuItem_path;
   private readonly static PropertyInfo s_ScriptingMenuItem_priority;
   private readonly static PropertyInfo s_ScriptingMenuItem_isSeparator;

   static MenuItemInternals()
   {
      var eapm = Array.Empty<ParameterModifier>();
      var eat = Array.Empty<Type>();
      s_GetMenuItems = typeof(Menu).GetMethod("GetMenuItems", BindingFlags.Static | BindingFlags.NonPublic, null, new Type[] { typeof(string), typeof(bool), typeof(bool) }, eapm);
      if (s_GetMenuItems == null) Debug.LogError("(Editor) s_GetMenuItems == null");

      var scriptingMenuItemType = Type.GetType("UnityEditor.ScriptingMenuItem, UnityEditor.CoreModule");
      if (scriptingMenuItemType == null) Debug.LogError("(Editor) scriptingMenuItemType == null");

      s_ScriptingMenuItem_path = scriptingMenuItemType?.GetProperty("path", BindingFlags.GetProperty | BindingFlags.Public | BindingFlags.Instance, null, typeof(string), eat, eapm);
      if (s_ScriptingMenuItem_path == null) Debug.LogError("(Editor) s_ScriptingMenuItem_path == null");

      s_ScriptingMenuItem_isSeparator = scriptingMenuItemType?.GetProperty("isSeparator", BindingFlags.GetProperty | BindingFlags.Public | BindingFlags.Instance, null, typeof(bool), eat, eapm);
      if (s_ScriptingMenuItem_isSeparator == null) Debug.LogError("(Editor) s_ScriptingMenuItem_isSeparator == null");

      s_ScriptingMenuItem_priority = scriptingMenuItemType?.GetProperty("priority", BindingFlags.GetProperty | BindingFlags.Public | BindingFlags.Instance, null, typeof(int), eat, eapm);
      if (s_ScriptingMenuItem_priority == null) Debug.LogError("(Editor) s_ScriptingMenuItem_priority == null");
   }
}

使用例

foreach (var menuItem in MenuItemInternals.UnityEditor_Menu_GetMenuItems(
    menuPath: "Assets/Create",
    includeSeparators: true,
    localized: false
    ))
{
   Debug.Log($"'{menuItem.Path}': {menuItem.Priority}");
}

出力

2021.3.25f(Personal)での、3D/built-inプロジェクトのデフォルトの場合の出力です。

'Assets/Create/Folder': 18
'Assets/Create/C# Script': 81
'Assets/Create/2D': 81
'Assets/Create/2D/Physics Material 2D': 12
'Assets/Create/Visual Scripting': 81
'Assets/Create/Visual Scripting/State Graph': 81
'Assets/Create/Visual Scripting/Script Graph': 81
'Assets/Create/Shader': 83
'Assets/Create/Shader/Standard Surface Shader': 83
'Assets/Create/Shader/Unlit Shader': 84
'Assets/Create/Shader/Image Effect Shader': 85
'Assets/Create/Shader/Compute Shader': 90
'Assets/Create/Shader/Ray Tracing Shader': 93
'Assets/Create/Shader Variant Collection': 83
'Assets/Create/Testing': 83
'Assets/Create/Testing/Tests Assembly Folder': 83
'Assets/Create/Testing/C# Test Script': 83
'Assets/Create/Playables': 87
'Assets/Create/Playables/Playable Behaviour C# Script': 87
'Assets/Create/Playables/Playable Asset C# Script': 88
'Assets/Create/Assembly Definition': 91
'Assets/Create/Assembly Definition Reference': 93
'Assets/Create/Text': 105
'Assets/Create/Text/Font Asset': 100
'Assets/Create/Text/Font Asset Variant': 105
'Assets/Create/Text/Sprite Asset': 150
'Assets/Create/Text/Text StyleSheet': 200
'Assets/Create/Text/Color Gradient': 250
'Assets/Create/TextMeshPro': 105
'Assets/Create/TextMeshPro/Font Asset': 100
'Assets/Create/TextMeshPro/Font Asset Variant': 105
'Assets/Create/TextMeshPro/Sprite Asset': 110
'Assets/Create/TextMeshPro/Color Gradient': 115
'Assets/Create/TextMeshPro/Style Sheet': 120
'Assets/Create/Scene': 201
'Assets/Create/Scene Template': 201
'Assets/Create/Scene Template From Scene': 201
'Assets/Create/Scene Template Pipeline': 202
'Assets/Create/Prefab': 202
'Assets/Create/Prefab Variant': 203
'Assets/Create/Audio Mixer': 215
'Assets/Create/Material': 301
'Assets/Create/Lens Flare': 303
'Assets/Create/Render Texture': 304
'Assets/Create/Lightmap Parameters': 305
'Assets/Create/Lighting Settings': 306
'Assets/Create/Custom Render Texture': 307
'Assets/Create/Animator Controller': 401
'Assets/Create/Animation': 402
'Assets/Create/Animator Override Controller': 403
'Assets/Create/Avatar Mask': 404
'Assets/Create/Timeline': 450
'Assets/Create/Signal': 451
'Assets/Create/Physic Material': 501
'Assets/Create/GUI Skin': 601
'Assets/Create/Custom Font': 602
'Assets/Create/Legacy': 701
'Assets/Create/Legacy/Cubemap': 701
'Assets/Create/UI Toolkit': 701
'Assets/Create/UI Toolkit/Style Sheet': 603
'Assets/Create/UI Toolkit/TSS Theme File': 604
'Assets/Create/UI Toolkit/Default Runtime Theme File': 605
'Assets/Create/UI Toolkit/UI Document': 610
'Assets/Create/UI Toolkit/Panel Settings Asset': 701
'Assets/Create/UI Toolkit/Editor Window': 701
'Assets/Create/UI Toolkit/Text Settings': 1000
'Assets/Create/Search': 1000
'Assets/Create/Search/Assets Index': 1000
'Assets/Create/Search/Prefabs Index': 1000
'Assets/Create/Search/Scenes Index': 1000
'Assets/Create/Brush': 1000
'Assets/Create/Terrain Layer': 1000

パラメータについて

「includeSeparators」がtrueだと、

2D/
2D/A
2D/B

みたいなときに、「2D」自体も含めて返します。そのとき「2D」はIsSeparatorがtrueになる
。「2D」のpriority知りたいときとかはtrueにするといいみたいです。(たぶん)
「localized」というのもあって普通にうれしい。

脚注
  1. https://github.com/Unity-Technologies/UnityCsReference/blob/master/Editor/Mono/Menu.bindings.cs ↩︎

Discussion