選択中のアセット毎にInspectorウインドウを開く機能を作成する

[PONOS Advent Calendar 2020](https://qiita.com/advent-calendar/2020/ponos)の4日目の記事です。

昨日は[@nissy_gp](https://qiita.com/nissy_gp)さんの[Firebase Authenticationの認証とアカウントリンク](https://qiita.com/nissy_gp/items/115854812d85770ff9d9)でした。

# はじめに

早速ですが、Unity上で**複数のアセットのインポート設定を比較しながら確認したい**シチュエーションがあると思います。
こうした場合、大抵次のような手順を踏んでInspectorウインドウを複数立ち上げて比較しますよね。
（ここでは仮に比較したいアセットを「アセットA」「アセットB」とします。）

**手順1: Projectウインドウ上で「アセットA」を選択する**
**手順2: Inspectorウインドウをロックする**
**手順3: ケバブメニューから「Add Tab/Inspector」を選択する**
**手順4: Projectウインドウ上で「アセットB」を選択する**
**手順5: 2つのInspectorウインドウにそれぞれ表示された「アセットA」と「アセットB」を比較する**

ケバブメニューからのInspectorウインドウの追加やロックの有効化など、地味に手間がかかる操作が多いですよね…
今回はこの操作を効率化できないかと考えてみました。

なお、本記事の実行環境は`Unity 2019.4.8f1`となります。

# Inspectorウインドウ操作の効率化

## 効率化のコンセプト

さて、今回の効率化のコンセプトですが、上記に記した操作手順の中で最も手間のかかる

> **手順2: Inspectorウインドウをロックする**
> **手順3: ケバブメニューから「Add Tab/Inspector」を選択する**

の自動化を試みてみようと思います。
機能実装によりこの手順を自動化することにより、上記の手順は以下のように効率化されることになります。

**手順1: Projectウインドウ上で「アセットA」を選択する**
**<font color=#ff0000>手順2: 今回の機能を使ってInspectorウインドウをロックして新しいInspectorウインドウを作成</font>**
**手順3: Projectウインドウ上で「アセットB」を選択する**
**手順4: 2つのInspectorウインドウにそれぞれ表示された「アセットA」と「アセットB」を比較する**

手順が減りましたが、まだ少し効率化できそうな気がします。

> **手順1: Projectウインドウ上で「アセットA」を選択する**
> **手順2: 今回の機能を使ってInspectorウインドウをロックして新しいInspectorウインドウを作成**
> **手順3: Projectウインドウ上で「アセットB」を選択する**

比較対象のアセットを順番に一つずつ選択してInspectorウインドウの表示を固定していますが、この操作を短縮できないか検討してみます。
Projectウインドウ上でアセットを選択するたびにInspectorウインドウの操作するのは手間なので、比較対象のアセットを一度にすべて選択したあと、それらに対してまとめてInspectorウインドウに関する操作を行ってみてはどうでしょう。

**手順1: Projectウインドウ上で「アセットA」「アセットB」を選択する**
<font color=#ff0000>**手順2: 今回の機能を使って「アセットA」「アセットB」をそれぞれ表示するInspectorウインドウを開く</font>**
**手順3: 2つのInspectorウインドウにそれぞれ表示された「アセットA」と「アセットB」を比較する**

いい感じに手順が短縮できましたね。
今回はこのコンセプトで機能の実装を検討していきましょう。

## スクリプトからInspectorウインドウを開く

さて、コンセプトが決まったところで次に具体的な実現方法について見ていきましょう。
まずは今回の効率化の肝である「スクリプトからのInspectorウインドウの操作」について確認していきたいと思います。

Unityには`InspectorWindow`というまさにInspectorウインドウを操作するためのクラスが用意されているのでこれを利用します。
ですが、このクラスはUnityEditorにinternalとして定義されているため、通常の方法はアクセスすることができません。幸いにも、Unity公式が公開してくれているUnityエディタのソースコードがあるので、そちらを参考にしながらリフレクションでアクセスしていこうと思います。

[UnityCsReference/InspectorWindow.cs at master · Unity-Technologies/UnityCsReference · GitHub](https://github.com/Unity-Technologies/UnityCsReference/blob/master/Editor/Mono/Inspector/InspectorWindow.cs)

### InspectorWindow型を取得する

```c#
var inspectorWindowType = Assembly.Load("UnityEditor").GetType("UnityEditor.InspectorWindow");
```

### InspectorWindow型からInspectorウインドウを生成する

```c#
var inspectorWindow = EditorWindow.CreateInstance(inspectorWindowType) as EditorWindow;
inspectorWindow.Show(true);
```

### Inspectorウインドウをロックする

```c#
var flags = BindingFlags.Public | BindingFlags.Instance;
var isLockedPropertyInfo = inspectorWindowType.GetProperty("isLocked", flags);
isLockedPropertyInfo.SetValue(inspectorWindow, true); // isLocked == trueでロック状態となる。
```

静止画だと説明しづらいのでスクリーンショットを載せられませんでしたが、無事にスクリプトからInspectorウインドウを操作することができました。

## 選択されたアセット毎にInspectorウインドウを開く

Inspectorウインドウをスクリプトから操作する方法が確認できましたので、続いて選択中のアセット毎にInspectorウインドウを開く処理を作成していきましょう。

選択中のアセットの情報を取得するためには`Selection.objects`を利用します。
こちらはエディタ拡張でよく使われる機能なので、ご存知の方も多いかと思います。

[Selection-objects - Unity スクリプトリファレンス](https://docs.unity3d.com/ja/2019.4/ScriptReference/Selection-objects.html)

Inspectorウインドウは現在選択中のアセットの情報を表示する挙動ですので、`Selection.objects`を変更することにより、Inspectorウインドウ内の表示を切り替えることができます。先に紹介した`InspectorWindow`クラスの機能と組み合わせることで、現在選択中のアセット毎に表示がロックされたInspectorウインドウを生成できるようになります。
コードは以下です。

```c#
for (int i = 0; i < Selection.objects; ++i)
{
    var selectedObject = Selection.objects[i];
    var inspectorWindow = EditorWindow.CreateInstance(inspectorWindowType) as EditorWindow;

    var isLockedPropertyInfo = inspectorWindowType.GetProperty("isLocked", BindingFlags.Public | BindingFlags.Instance);
    Selection.objects = new UnityEngine.Object[] { selectedObject };
    isLockedPropertyInfo.SetValue(inspectorWindow, true);

    inspectorWindow.Show(true);
}
```

## 仕上げ

さて、最後の仕上げです。
今回作成した機能を実行しすくするため、キーボードショートカットを登録します。
また、Inspectorウインドウの操作後にオブジェクトの選択状態が操作前と同じ状態になるように対応しておきます。

これらの仕上げを経て完成したコード全文が以下となります。

```c#
using System;
using System.Reflection;
using UnityEngine;
using UnityEditor;

/// <summary>
/// ロックされたInspectorウインドウを開くエディタ拡張。
/// </summary>
public class LockedInspectorOpener
{
    /// <summary>
    /// 選択中のオブジェクトに対して、Inspectorウインドウを開く。
    /// command + shift + iのキーボードショートカットでも実行できる。
    /// </summary>
    [MenuItem("Locked Inspector/Open Selected Objects #%i")]
    static void OpenSelectedObjects()
    {
        var inspectorWindowType = Assembly.Load("UnityEditor").GetType("UnityEditor.InspectorWindow");

        int selecedObjectsCount = Selection.objects.Length;
        var originalSelectedObjects = new UnityEngine.Object[selecedObjectsCount]; // 選択中のオブジェクトを保存しておく。
        Array.Copy(Selection.objects, originalSelectedObjects, selecedObjectsCount);

        for (int i = 0; i < selecedObjectsCount; ++i)
        {
            var selectedObject = originalSelectedObjects[i];
            var inspectorWindow = EditorWindow.CreateInstance(inspectorWindowType) as EditorWindow;

            var isLockedPropertyInfo = inspectorWindowType.GetProperty("isLocked", BindingFlags.Public | BindingFlags.Instance);
            Selection.objects = new UnityEngine.Object[] { selectedObject }; // Inspectorウインドウに表示するオブジェクトを切り替える。
            isLockedPropertyInfo.SetValue(inspectorWindow, true); // 現在のInspectorウインドウの表示をロックする。

            inspectorWindow.Show(true);
        }

        Selection.objects = originalSelectedObjects; // 操作前の選択状態に戻す。
    }
}
```

# まとめ

以上、Unityエディタ内でも触れることの多いInspectorウインドウに関する操作を効率化した例でした。

今回紹介した機能自体は小さなものですが、このような効率化の積み重ねが確実に日々の作業の削減に繋がります。
今後も「地味に手間がかかる作業」についての効率化を試み、紹介していこうと思いますので、どうぞよろしくお願い致します。

明日は[@kerimeka](https://qiita.com/kerimeka)さんです！