【Unity】UI Toolkitを使ったカスタムエディタでスクリプトファイルのpingが動くようにする【エディタ拡張】

# はじめに
* [【Unity】デフォルトのカスタムエディタを実装可能なisFallbackフラグの紹介【CustomEditor】](https://qiita.com/su10/items/9d83b6b5c6ee43b08e4d)

上記の記事の最後にある

>あと実は紹介した DefaultEditor の実装にも問題があり、一番上に描画されるスクリプトの PropertyField をクリックしてもスクリプトファイルがpingされない問題があるのですが、それについてはまた別の記事で書こうと思います。

これについてです。

# 環境
* Unity2021.3.0f1

Unity2022.1.0f1(Apple Silicon版)では再現しなかったので修正されたようです。

# UI Toolkitを使ったときのInspectorの問題

```SampleComponentEditor.cs
#if UNITY_EDITOR
using UnityEditor;
using UnityEditor.UIElements;
using UnityEngine.UIElements;

[CustomEditor(typeof(SampleComponent), true)]
public class SampleComponentEditor : Editor
{
    public override VisualElement CreateInspectorGUI()
    {
        var container = new VisualElement();

        // IMGUI同様のInspectorを実装
        InspectorElement.FillDefaultInspector(container, serializedObject, this);

        return container;
    }
}
#endif
```

これで描画されるInspectorではスクリプトの名前のところをクリックしてもスクリプトがpingされません。

![Oct-12-2022 13-37-50.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/27863/487a509b-172c-ccb5-2be3-05b0bb70418a.gif)
*上が通常のデフォルトエディタ (クリックでpingされる)
下が UI Toolkit を使ったカスタムエディタ (pingされない)*

# 解決方法： `PropertyField` の代わりに `ObjectField` をカスタムして使う
この問題については以下のフォーラムの投稿が参考になります。

* [When are PropertyField contents created?](https://forum.unity.com/threads/when-are-propertyfield-contents-created.1080806/)

以下コード引用。

```cs
private static VisualElement CreateScriptReadonlyField(SerializedProperty property)
    {
      VisualElement propertyField = new VisualElement() { name = $"PropertyField:{property.propertyPath}" };
      ObjectField objectField = new ObjectField("Script") { name = "unity-input-m_Script" };
      objectField.BindProperty(property);
      propertyField.Add(objectField);
      propertyField.Q(null, "unity-object-field__selector")?.SetEnabled(false);
      propertyField.Q(null, "unity-base-field__label")?.AddToClassList("unity-disabled");
      propertyField.Q(null, "unity-base-field__input")?.AddToClassList("unity-disabled");
      return propertyField;
    }
```

`SetEnabled(false)` で右側のボタンを無効にしているのと、 `AddToClassList("unity-disabled")` でdisabled状態のスタイルシートを適用しています。

`PropertyField` だと `CreateInspectorGUI()` の時点で要素にアクセスできない（自分もこれで悩んだ）ので、 `ObjectField` の代わりに `PropertyField` を使うようです。

　
＊＊＊＊＊
　

上記を参考に、スクリプトのpingも可能なデフォルトのカスタムエディタを書くとこんな感じです。

```DefaultEditor.cs
#if UNITY_EDITOR
using UnityEditor;
using UnityEditor.UIElements;
using UnityEngine;
using UnityEngine.UIElements;

[CustomEditor(typeof(Object), true, isFallback = true)]
public class DefaultEditor : Editor
{
    private static VisualElement CreateScriptReadonlyField(SerializedProperty property)
    {
        var propertyField = new VisualElement { name = $"PropertyField:{property.propertyPath}" };
        var objectField = new ObjectField("Script") { name = "unity-input-m_Script" };
        objectField.BindProperty(property);
        // スペースキーを押してもスクリプトを選択するウィンドウを表示しないようにする
        objectField.focusable = false;
        propertyField.Add(objectField);
        propertyField.Q(null, "unity-object-field__selector")?.SetEnabled(false);
        propertyField.Q(null, "unity-base-field__label")?.AddToClassList("unity-disabled");
        propertyField.Q(null, "unity-base-field__input")?.AddToClassList("unity-disabled");
        return propertyField;
    }

    public override VisualElement CreateInspectorGUI()
    {
        var container = new VisualElement();
        var iterator = serializedObject.GetIterator();

        if (iterator.NextVisible(true))
        {
            do
            {
                var serializedProperty = iterator.Copy();
                VisualElement propertyField;

                if (iterator.propertyPath == "m_Script" && serializedObject.targetObject != null)
                {
                    propertyField = CreateScriptReadonlyField(serializedProperty);
                }
                else
                {
                    propertyField = new PropertyField(iterator.Copy()) { name = "PropertyField:" + iterator.propertyPath };
                }

                container.Add(propertyField);
            }
            while (iterator.NextVisible(false));
        }

        return container;
    }
}
#endif
```

元のコードだとフォーカスが当たってるときに `Space` キーを押すとスクリプトを選択するウィンドウが表示されてしまうので、

```cs
objectField.focusable = false;
```

を入れてあります。

# 雑感
件の投稿は「こんな実装してみたけど問題ない？」という感じの内容なのですが、まじめにやろうと思ったら `BaseField<UnityEngine.Object>` を継承してクラスを手実装する必要がありそうです。

でもまぁUnityのバージョン上げれば解決される問題だし、これでいいかなという感じ。