Иногда хочется закрепить окно инспектора, чтобы заполнить сразу несколько полей в компонентах, например. Но кнопка, отвечающая за это, слишком маленькая, ее неудобно искать, а потом нужно ее еще раз найти чтобы разблокировать окно. Гораздо удобнее это было бы сделать с помощью хоткея.

В Unity можно добавить любую статическую функцию в пункт меню, делается это с помощью атрибута [MenuItem("Menu name/Submeny/Menu point %l")]

Одноуровневые меню ([MenuItem("Menu point %l")]) не допускаются.

Также к этим пунктам можно привязать хоткеи в формате специальный символ + буква. %l означает ctrl + l.

Список символов:

  • % ctrl
  • # shift
  • & alt
  • _ отсутствие спецсимвола

Также можно привязать к специальным кнопкам с помощью ключевых слов: LEFT, RIGHT, UP, DOWN, F1 … F12, HOME, END, PGUP, PGDN

Стандартный Hello World

Создадим в любом удобном месте скрипт. Например, в Assets\Scripts\EditorTools\Hello.cs

public static class Hello
{
    [MenuItem("Hello/World %&h")]
    private static void HelloWorld()
    {
        Debug.Log("Hello World!");
    }
}

Теперь при выборе соответствующего пункта в меню или при нажатии Ctrl + Alt + H в консоль будет выводиться “Hello World!”.

Блок окна инспектора

Сразу весь код:

[MenuItem("UI Helpers/Block Inspector %l")]
private static void ToggleInspectorLock()
{
    Type inspectorWindowType = Assembly.GetAssembly(typeof(Editor)).GetType("UnityEditor.InspectorWindow");

    var foundWindows = Resources.FindObjectsOfTypeAll(inspectorWindowType);
    if(foundWindows.Length == 0)
    {
        Debug.LogError("Cant find inspector window. Please check that inspector window is open");
        return;
    }

    var editorWindow = (EditorWindow)foundWindows.FirstOrDefault(window => ((EditorWindow)window).titleContent.text.Equals("Inspector"));
    if (editorWindow == null)
    {
        Debug.LogError("Cant find inspector window. Please check that inspector window is open");
        return;
    }

    PropertyInfo propertyInfo = inspectorWindowType.GetProperty("isLocked");
    bool isWindowCurrentlyLocked = (bool) propertyInfo.GetValue(editorWindow);
    propertyInfo.SetValue(editorWindow, !isWindowCurrentlyLocked, null);
    editorWindow.Repaint();
}

Теперь разберем его по частям.

Type inspectorWindowType = Assembly.GetAssembly(typeof(Editor)).GetType("UnityEditor.InspectorWindow");

Для того чтобы получить окно инспектора мы будем использовать рефлексию. А для использования рефлексии нам нужен класс окна инспектора. В саму рефлексию я углубляться не буду, это тема для другого поста (или даже блога).

Чтобы получить классы других распространенных типов окна, можно использовать следующие строки (взял отсюда):

  • Project: UnityEditor.ProjectBrowser
  • Hierarchy: UnityEditor.SceneHierarchyWindow
  • Scene: UnityEditor.SceneView
  • Game: UnityEditor.GameView
  • Console: UnityEditor.ConsoleWindow
var foundWindows = Resources.FindObjectsOfTypeAll(inspectorWindowType);
if(foundWindows.Length == 0)
{
    Debug.LogError("Cant find inspector window. Please check that inspector window is open");
    return;
}

Теперь, когда у нас есть класс инспектора, мы можем попробовать найти нужное окно. В этом коде мы получаем все открытые на данный момент окна инспектора. Их может быть несколько, если просто открыть два окна инспектора в юнити, или сделать свое кастомное окно инспектора.

var editorWindow = (EditorWindow)foundWindows.FirstOrDefault(window => ((EditorWindow)window).titleContent.text.Equals("Inspector"));
if (editorWindow == null)
{
    Debug.LogError("Cant find inspector window. Please check that inspector window is open");
    return;
}

Здесь мы получаем непосредственно окно инспектора. Я добавил дополнительную проверку, что тайтл окна соответствует стандартному, но, в целом, это необязательно и можно использовать просто (EditorWindow)foundWindows.FirstOrDefault();. Проверка добавлена на случай, если Resources.FindObjectsOfTypeAll() вернет больше одного окна, или если будет открыто какое-нибудь кастомное окно инспектора, а стандартное будет закрыто.

PropertyInfo propertyInfo = inspectorWindowType.GetProperty("isLocked");
bool isWindowCurrentlyLocked = (bool) propertyInfo.GetValue(editorWindow);
propertyInfo.SetValue(editorWindow, !isWindowCurrentlyLocked, null);

Смотрим, в каком сейчас состоянии окно и, если заблокировано, то снимаем блок, и наоборот.

editorWindow.Repaint();

Заново рендерим окно. Нужно, чтобы изменения на нем отобразились, без этого кнопка замка, отображающая статус блокировки, не будет менять свой статус.

Дополнительные ссылки:

Проверено на Unity 2020.3.12.f1.