Unityエディタ拡張作ってみた advent 2018

# はじめに
作ってみたエディタ拡張をいろいろと紹介したいと思います。
UniRxでObservableにしてみたEditorWindowやSerializeFieldに関するものなどいろいろ作ってみたのでどうぞみてみてください。

# 環境
* Unity2018.3.0f2
  * Scripting Runtime Version (.NET 4.X Equivalent)
* UniRx ver6.2.2

# 作ったみたリスト
今回作ってみたものリストとなります。
作ったコードは個人のGitHubGistにて公開しましたのでご自由にご利用ください。

* [UniRxなエディターウィンドウ](https://gist.github.com/gingerbread4/e03b676f4faa13df1bdac7bc29cb8155)
* [リフレクションでSerializeFieldをゲットしnullだったらエラーを出す](https://gist.github.com/gingerbread4/35da73c00c9806c0b1a77c5fb9c8cb6f)
* [SerializeField付きスクリプト生成ウィザード](https://gist.github.com/gingerbread4/d5e3d7199f8ba18f0f4aa03fb4dae4ca)
* [EditorのAssemblyDefinitionを右クリックメニューで追加できるようにしてみた](https://gist.github.com/gingerbread4/8a96dbcc449529fbbedf43467e8189bc)

### 解説
作ってみたものを解説いたします。

## UniRxでエディターウィンドウをObservableにしてみた
コード一部略

```csharp
public abstract class ObservableWindowBase : EditorWindow
{
    Subject<Unit> update;
    Container Root = new Container();

    public IObservable<Unit> UpdateAsObservable()
    {
        return update ?? (update = new Subject<Unit>());
    }

    public TElement AddElement<TElement>(TElement element) where TElement : IElement
    {
        return Root.AddElement<TElement>(element);
    }

    void Update()
    {
        if (update != null) { update.OnNext(Unit.Default); }
    }

    void OnGUI()
    {
        (Root as IElement).Draw();
    }
}
```

```csharp

public class ObservableTestEditorWindow : ObservableWindowBase
{
    [MenuItem("Window/ObservableTestEditorWindow")]
    static void Init()
    {
        GetWindow<ObservableTestEditorWindow>();
    }

    Toggle updateToggle;

    void OnEnable()
    {
        new Button(this, "label1")
            .OnClickAsObservable()
            .Subscribe(_ => { Debug.Log("clicked button 1"); });

        new Button(this, "label2")
            .OnClickAsObservable()
            .Subscribe(_ => { Debug.Log("clicked button 2"); });

        updateToggle = new Toggle(this, false, "isUpdate");
        updateToggle.OnValueChangedAsObservable().Subscribe(v =>
        {
            Debug.Log("on value changed to:" + v);
        });

        UpdateAsObservable()
            .Where(x => updateToggle.RValue.Value)
            .Subscribe(_ =>
        {
            Debug.Log("Update...");
        });
    }
}
```
```csharp

public class Button : IElement
{
    Subject<Unit> onClick;
    string label;

    public Button(ObservableWindowBase window, string label)
    {
        window.AddElement(this);
        this.label = label;
    }

    void IElement.Draw()
    {
        if (GUILayout.Button(label)) onClick?.OnNext(Unit.Default);
    }

    public IObservable<Unit> OnClickAsObservable()
    {
        return onClick ?? (onClick = new Subject<Unit>());
    }
}
```
実際にObservableになっているのはEditorWindow.Updateのみですが、
UniRxのコードっぽく書けるようにできました。
しかしながら試しに作ってみた程度のものでございます。。

## SerializeFieldをリフレクションでゲットしてnullならエラー
現在のシーンでSerializeFieldのオブジェクトがnullのものがあったらエラーを出します。

```csharp
    // 必要なusing
    using UnityEngine;
    using UnityEditor;
    using System.Reflection;
    using System.Linq;

    [MenuItem("Test/SerializeField Null Check in this Scene")]
    static void SerializeFieldNullCheck()
    {
        foreach (var component in GameObject.FindObjectsOfType<Component>())
        {
            var fields = component.GetType()
                .GetFields(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.FlattenHierarchy)
                .SelectMany(x => x.CustomAttributes.Where(t => t.AttributeType == typeof(SerializeField)).Select(_ => x));

            foreach (var field in fields)
            {
                var value = field.GetValue(component);
                UnityEngine.Assertions.Assert.IsNotNull(value, "Error! [SerializeField] " + field.Name + " is null !!");
            }
        }
    }
```

### リフレクションでSerializeFieldをゲットする
`FieldInfo`の`CustomAttributes`で`AttributeType`がSerializeFieldなものでとってこれます。
そのフィールドの`GetValue`の値がnullだったらエラー表示する、という感じにすればOKです。

### 用途
外れてはいけないSerializeFieldがいつのまにか（誰かが気づかぬまま?!）外してしまっていることに気づかないことがあったとしても、上のようなテストコードを書いてCIするといいかも?!

## 追記(2021/09)
`UnityEngine.Object`がnullかどうかの判定でUnity2019.4系では
SerializeFieldがnullかどうかは下記のようにとらないといけない模様でした。

```
var value = field.GetValue(component);
if (field.FieldType.IsSubclassOf(typeof(UnityEngine.Object)) &&
                    value as UnityEngine.Object == null)
```


## SerializeFieldをリンクしてスクリプト生成ウィザード
スクリプトの生成とSerializeFieldのリンクを同時に行ってくれるスクリプト生成ウィザードです。
![2018-12-20_01h49_24.png](https://qiita-image-store.s3.amazonaws.com/0/148284/78ec024e-1265-5338-9fc0-0d1cb5a73aac.png)

## Editorフォルダ以下に置くEditorのAssemblyDefinitionを右クリックメニューから作成できるようにするエディタ拡張
![2018-12-15_22h05_12.png](https://qiita-image-store.s3.amazonaws.com/0/148284/6b1fd9b9-a71e-b60d-7980-59267bdd33c0.png)
EditorのAssemblyDefinitionを作成するときの手順が少し面倒だったため一発で作成できるのをつくってみました。

## まとめ
いかがでしたでしょうか。
役に立ちそうなものがあれば幸いです。