XMLとか知らねぇよ！！C#だけでUIElements式エディタ拡張をする入門～基礎編～


#はじめに
こんにちは、アドベントカレンダー2日目担当の避雷です。お仕事でエディタ拡張する必要があり、たまたまバージョン的にもUIElementsが使える感じだったので試しに使ってみたところ結構便利だったので紹介します

- 基礎編（ここ）
- style編

#UIElementsとは
Unityが公式でサポートしている新しいUIデザインの手法です。xamlやCSSと同様の記法によってUIのデザインができます。Unity的にはuGUIからこちらへの移行を試みているようで、uGUIの方はスタメンから外されてpackageManagerへの流刑を食らっています。主観としてはUnityはゲームエンジンからソフトウェア開発プラットフォームになろうとしていて、ゲーム以外のUI設計を可能にしたい、みたいなモチベなのかなと思いました。
https://blogs.unity3d.com/jp/2019/04/23/whats-new-with-uielements-in-2019-1/
##メリット
- OnGUIコールバックベースからの脱却
-  UIにeventを登録する感じでかけるので可読性が上がる。
-  コードが書きやすい。
-  xamlによってデザインするタイプの開発経験があると同じようなスタイルで触れる。

##デメリット
- 必要になる技術が多い 
- プロジェクトメンバー全員がxamlとかCSSとか触れるスキルセットじゃないと属人性がキツくなりがち
- 普通にuGUIの代替にはならないと思う

#実装
##導入
右クリックから「UIElements Editor Window」を選択して元となるスクリプトを生成しましょう。Editorフォルダ無いじゃないと生成出来ないよって怒られるので注意。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256231/94e69522-8a72-fd96-d3d7-568561600761.png)
##生成されるウィンドウを見てみよう
デフォルトだとWindow/UIElementから先ほど作成したwindowを呼び出すことができます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256231/31ccea78-30c8-c941-dc6b-588615078625.png)
HelloWorldって生成されていますね。
##生成されたコードを読んでみよう
先ほど生成したC#を覗いてみましょう。

```C#:Test.cs
using UnityEditor;
using UnityEngine;
using UnityEngine.UIElements;
using UnityEditor.UIElements;


public class Test : EditorWindow
{
//メニューバーから呼び出すためのattribute
    [MenuItem("Window/UIElements/Test")]
    public static void ShowExample()
    {
        Test wnd = GetWindow<Test>();
//↓タイトルの設定
        wnd.titleContent = new GUIContent("Test");
    }

    public void OnEnable()
    {
//rootVisualElement（表示されるUIすべての親となるVisualElementの取得）
        VisualElement root = rootVisualElement;
//VisualElementの一つ、ラベルを生成
        VisualElement label = new Label("Hello World! From C#");
//rootの子要素として追加、デフォルトだと子要素は上から下にstackされていく
        root.Add(label);
    }
}
```

なるほど、C#でもrootを根とした樹状構造を構築してUIを表現することが出来るみたいです。早速色々作ってみることにしましょう。

##ラベルをつける
これに関してはそのままLabelというクラスがあるのでこれをインスタンスしてAppendして行きます。

```C#
        Label label = new Label("Hello World! From C#");
        root.Add(label);
```

コレでラベルを付けることが出来ます。`label.text` を用いれば後からテキストの変更もできます。

```C#
        Label label = new Label("Hello World! From C#");
        root.Add(label);
        label.text = "aa";
```



##ボタンをつける
ButtonというクラスがあるのでこれもインスタンスしてAppendします。

```C#
        Button button = new Button(() => Debug.Log("aaa"));
        button.text = "Button";
        root.Add(button);
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256231/ede9ddd2-2f15-a25f-4e60-1dc206ff401e.png)


Buttonコンストラクタの第一引数はイベントです。`button.text`でボタンのテキストを変更することが出来ます。
##入力ボックスを作る
TextFieldコンポネントを利用します。

```C#
        TextField textField = new TextField("textField");
        root.Add(textField);
```

コンストラクタの引数はラベルの名前です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256231/9a1a3944-f446-d5c5-300b-a3c89ec714dd.png)

入力された値を取るには`textField.value`を用います。
`textField.RegisterValueChangedCallback`を用いて変更時のコールバックを設定することもできます。

```C#
        textField.RegisterValueChangedCallback(x => Debug.Log(x.previousValue+ "=>" + x.newValue));
```

###複数行入力に対応したいときは
`textField.multiline`から入力形式を変更することが出来ます。ついでに `style.height`から入力欄の高さを調整しておくとそれっぽいでしょう。

```C#
        textField.multiline = true;
//レイアウトにかかわる部分はXX.styleから変更できる
        textField.style.height = 50;
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256231/2f2e74c6-2a72-f311-5149-e0f22b64a2fd.png)
##ドロップダウンを作る
PopUpList<T>というクラスがあるのでそれを使いましょう。

```C#
        List<string> choices = new List<string>() { "A", "B", "C" };
        PopupField<string> popupField = new PopupField<string>(choices,0);
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256231/6831573e-ac0c-a364-88a0-af223b67e366.png)

コンストラクタの引数は第一が選択肢、第二が初期値です。
選択中のIdを取るには `popupField.index`、選択中の値を取るには`popupField.value`を用います。
また、TextField同様コールバックを登録することもできます。

```C#
popupField.RegisterValueChangedCallback(x => Debug.Log(x.newValue));
```

##VisualElementを使う
VisualElementはVisualElementを親として持つことが出来ます。Unityのオブジェクト群やCSS同様にヒエラルキー構造を持たせることが出来るということですね。

```C#
        var element = new VisualElement();
```

試しにmarginを設定してみます。

```C#
        element.style.marginTop = 10;
        element.style.marginBottom = 10;
        element.style.marginLeft = 10;
        element.style.marginRight = 10;
```

ついでにborderLineも付けてみましょう

```C#
        element.style.borderColor = new StyleColor(Color.black);
        element.style.borderBottomWidth = 3;
        element.style.borderTopWidth = 3;
        element.style.borderLeftWidth = 3;
        element.style.borderRightWidth = 3;
```

先ほどまで作っていたUIの親をrootではなくこのelementにしてみます。

```C#
        // VisualElements objects can contain other VisualElement following a tree hierarchy.
        Label label = new Label("Hello World! From C#");
        element.Add(label);
        label.text = "aa";
        
        Button button = new Button(() => Debug.Log("aaa"));
        button.text = "Button";
        element.Add(button);
        
        TextField textField = new TextField("textField");
        textField.RegisterValueChangedCallback(x => Debug.Log(x.previousValue+ "=>" + x.newValue));
        element.Add(textField);

        textField.multiline = true;
        textField.style.height = 50;
        
        List<string> choices = new List<string>() { "A", "B", "C" };
        PopupField<string> popupField = new PopupField<string>(choices,0);
        element.Add(popupField);
        popupField.RegisterValueChangedCallback(x => Debug.Log(x.newValue));
        
        root.Add(element);

```

描画結果はこんな感じです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256231/ec1f64ab-6197-7437-a957-fcee36bee8d0.png)

親要素であるelementの中に子要素のUIが入っていることがわかると思います。

#おわりに
コレでC#オンリーでも雑にUIを組めるようになったのではないでしょうか？明日はUIをもうちょっとマシなレイアウトで表示するための「style」について考えてみましょう。