Unityエディタの中を見て理解を深める

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

昨日は@nissy_gpさんの「[YouTubeの英語の動画はどう見たほうが間違えないのか](https://qiita.com/nissy_gp/items/8f561767d3fca8d03421)」でした。

# はじめに

先日、「[Debug.Log()の全文をファイル出力し、<message truncated>で省略された内容をすぐに確認できるようにする](https://qiita.com/e73ryo/items/808107640ea77ec04a8a)」という記事を投稿しました。
この調査を進める中でUnityエディタ内のクラスにアクセスする機会があり、やり方について知っておくとエディタを拡張する際に便利かと思いましたので、共有いたします。

確認環境は`Unity 2019.4.32f1`です。

# Unityエディタの中を見る方法

通常、Unityエディタ内で使用されているクラス（例えば、Consoleを始めとした各ウインドウの制御など）は、利用することができません。
ですが、クラスの定義が判明していれば、C#のリフレクションの機能を使ってクラスを利用することができます。

では、Unityエディタ内のクラスの定義はどこで確認したら良いでしょうか。
実はUnity公式から実際に運用されているC#のコードが公開されています。
（確認する際は、自身が使用しているUnityのバージョンに合わせてブランチまたはタグを切り替えてください）
[GitHub - Unity-Technologies/UnityCsReference: Unity C# reference source code.](https://github.com/Unity-Technologies/UnityCsReference)

ライセンスは以下が適用されています。（対象コードは参照にのみ利用できます）
[Unity Reference-Only License (“License”) - Unity](https://unity3d.com/legal/licenses/Unity_Reference_Only_License)

このリポジトリのコードを読むことで、Unityエディタ内にどういったクラスが定義されているのかを知ることができます。

# サンプル

さて、ではUnityエディタ内のクラスを利用するサンプルとして、**「Consoleウインドウのフィルター欄に『PONOS』文字列を設定する」**機能を実装してみます。

## 型を取得する

まずは、リフレクションの処理を用いて`ConsoleWindow`の型を取得してみましょう。

Consoleウインドウの処理は`ConsolwWindow`クラスに実装されています。
[UnityCsReference/ConsoleWindow.cs at 2019.4 · Unity-Technologies/UnityCsReference · GitHub](https://github.com/Unity-Technologies/UnityCsReference/blob/2019.4/Editor/Mono/ConsoleWindow.cs)

`System.Refrection.Assembly`クラスを利用して`ConsoleWindow`が含まれるエディタ用のアセンブリオブジェクトを取得し、その中から対象となる型情報を取得しています。

```cs
// エディタ用のアセンブリを取得。
var assembly = typeof(Editor).Assembly;

// Consoleウインドウの型情報を取得。
var consoleWindowType = assembly.GetType("UnityEditor.ConsoleWindow");
```

## メソッド情報を取得する

次に、フィルター欄に文字列を設定するためのメソッド`ConsoleWindow.SetFilter()`を実行するために必要な、メソッド情報`MethodInfo`を探します。
メソッド情報を探すときは対象のメソッド名が必要となります。

また、対象となるメソッドの定義に応じて、適切に`BindingFlags`で検索方法を指定する必要があります。
今回対象となる`SetFilter()`メソッドは以下のように定義されているため、`BindingFlags`は`BindingFlags.Instance | BindingFlags.NonPublic`を指定します。

```cs
private void SetFilter(string filteringText)
```

※その他の`BindingFlags`の値は以下を参考にしてください。
[BindingFlags 列挙型 (System.Reflection) | Microsoft Docs](https://docs.microsoft.com/ja-jp/dotnet/api/system.reflection.bindingflags?view=net-6.0)

以上を踏まえて、メソッド情報を取得するためのコードは以下のようになります。

```cs
// SetFilter()メソッドの情報を取得する。
// フラグとして「インスタンスメンバである」「パブリックアクセス以外のメンバを含める」を渡しておく。
var bindingAttr = BindingFlags.Instance | BindingFlags.NonPublic;
var setFilterMethodInfo = consoleWindowType.GetMethod("SetFilter", bindingAttr);
```

## メソッドを実行する

最後にメソッド情報を元に、処理を実行します。
`MethodInfo`から処理を実行する際は、「メソッドを実行させるオブジェクト」を渡す必要があります。
`ConsoleWindow`オブジェクトは`EditorApplication.GetWindow()`から取得が可能です。

```cs
// メソッドの実行対象となるConsoleウインドウのオブジェクトを取得。
var consoleWindow = EditorWindow.GetWindow(consoleWindowType);

// メソッド情報を元に、対象のオブジェクトに対してメソッドを実行する。
setFilterMethodInfo.Invoke(consoleWindow, new object[] { "PONOS" });
```

## コード全文

説明は以上です。コードの全文は以下のようになります。

```cs
using System.Reflection;
using UnityEditor;

/// <summary>
/// Consoleウインドウのフィルター欄に文字列を設定するクラス。
/// </summary>
public static class ConsoleFilterSetter
{
    /// <summary>
    /// Consoleウインドウのフィルター欄に「PONOS」を設定する。
    /// </summary>
    [MenuItem("Console Filter/Set \"PONOS\"")]
    public static void SetPONOS()
    {
        // エディタ用のアセンブリを取得。
        var assembly = typeof(Editor).Assembly;

        // Consoleウインドウの型情報を取得。
        var consoleWindowType = assembly.GetType("UnityEditor.ConsoleWindow");

        // SetFilter()メソッドの情報を取得する。
        // フラグとして「インスタンスメソッド」「パブリックでない」を渡しておく。
        var bindingAttr = BindingFlags.Instance | BindingFlags.NonPublic;
        var setFilterMethodInfo = consoleWindowType.GetMethod("SetFilter", bindingAttr);

        // メソッドの実行対象となるConsoleウインドウのオブジェクトを取得。
        var consoleWindow = EditorWindow.GetWindow(consoleWindowType);

        // メソッド情報を元に、対象のオブジェクトに対してメソッドを実行する。
        setFilterMethodInfo.Invoke(consoleWindow, new object[] { "PONOS" });
    }
}
```

実行結果は以下です。

![PONOS動画.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/7345/2c0e3b2c-dd2d-3fb7-b0b5-a5397fb7103c.gif)

# まとめ

`UnityCsReference`リポジトリを見ることでUnityエディタ内の処理を追うことができるので、Unityに対する理解を一層深めることができると思います！

明日は@nisei275さんです！