ちゃこブログ

お絵かきとUnityとBlenderと日記

【Unity】UIElementsちょっとだけ入門してみた

 UnityEditor拡張に力を入れて学習してきた身としては、UIElementsはモノにしなくてはならない技術だという使命感はあります。しかし、CSSやらXMLやらにあまり馴染みがなく、USSとUXMLに向き合うのは結構なモチベーションがないと学習が捗らないものです…というのもIMGUIでGUIをゴリゴリ書くのもまだまだアリだと感じているからです(´・ω・`)

 このような理由でUIElementsの門をくぐれないでいる日々が長らく続いておりました。
Unity2019で生成できるサンプルや公式動画の解説を見てもピンとこなくて心が挫けそうになったとある日、あるサンプルを見つけたことでUIElementsへの入門にこぎつけることができました!

超おすすめUIElementsサンプル

github.com

 こちらのUIElementsサンプルの中にあるBasicDemoWindow.csにUIElementsのなんたるかを至極単純に示されておりました。どうやらUIElementsでは、IMGUIコードをUIElementsに組み込むことも、それにUSSを適用することも、C#書かずにUXMLを利用するやり方も何通りもの多種多様な実装が可能なようです。該当コードにはそのすべてがあり、非常にわかりやすかったです。
自由度が高いものって何から始めていいかわからないから入門が一番厳しいよね( ;∀;)

UIElementsを使ったUIのサンプルとして、以下のようなEditorWindowが実装されています。

f:id:charcotte:20191009180114p:plain

EditorWindowのテキストに書いてある通り、いろんな実装でこのUIが構成されています。
今回はユーザーの入力に対するコールバックの実装などは省いて、UIの実装部分だけ見ていきます。

Inline C#

var root = rootVisualElement;

var inlineVE = new VisualElement()
{
    style =
    {
        marginBottom = 2,
        paddingLeft = 4,
        flexDirection = FlexDirection.Row,
        backgroundColor = new Color(0.18f, 0.18f, 0.18f),
    }
};

// Label
inlineVE.Add(new Label()
{
    text = "Inline C#",
    style =
    {
        fontSize = 20,
        unityFontStyleAndWeight = FontStyle.Bold,
        width = 140
    }
});

// TextField
inlineVE.Add(new TextField()
{
    value = m_Tank.tankName,
    style =
    {
        fontSize = 20,
        unityFontStyleAndWeight = FontStyle.Bold,
        flexGrow = 1
    }
});

// IntegerField
inlineVE.Add(new IntegerField()
{
    value = m_Tank.tankSize,
    style =
    {
        fontSize = 20,
        unityFontStyleAndWeight = FontStyle.Bold,
        width = 100,
        backgroundColor = Color.blue
    }
});

root.Add(inlineVE);

 VisualElementだけでGUIを組み立てられるようです。IMGUIだと要素の大きさを変更する際はGUIStyleをゴニョゴニョする必要がありましたが、わかりやすい変数でUIをカスタマイズ出来そうです。ただ、この書き方はコードが冗長になるしぱっと見てどんなUIになるか分かりづらい感じがします。

C# + USS

var root = rootVisualElement;

var ussVE = new VisualElement() { name = "row" };
ussVE.AddToClassList("container");
ussVE.styleSheets.Add(Resources.Load<StyleSheet>("Basics/basics_styles"));

ussVE.Add(new Label() { text = "USS" });
ussVE.Add(new TextField() { value = m_Tank.tankName });
ussVE.Add(new IntegerField() { value = m_Tank.tankSize });

root.Add(ussVE);

 一気にシンプルなスクリプトになってますね。Inline C#ではstyleをスクリプトで指定していましたが、これらの指定をUSSにお任せできる感じでしょうか。VisualElementにUSSを流し込んで、LabelやTextFieldなどの子要素に適用されUIが組み立てられていくようです。
とりあえず今はUSSの書き方は置いておきます…

C# + USS + UXML

var root = rootVisualElement;

var visualTree = Resources.Load("Basics/basics_uxml") as VisualTreeAsset;
var uxmlVE = visualTree.CloneTree();

root.Add(uxmlVE);

 UXMLを使えば、VisualElementを書かずともこれだけのコードだけでUIが組めちゃうんですね。
とりあえず今はUXMLの書き方も置いておきます…

with IMGUI

var root = rootVisualElement;
var imguiContainer = new IMGUIContainer(() =>
{
    IMGUIDemoWindow.DemoOnGUI(m_Tank);
});

root.Add(imguiContainer);

 IMGUIContainerというのを使うと簡単にVisualElementのUIシステムに従来のIMGUIコードを適用させることができるようです。これはありがたいし超柔軟にUI描けそうですよね…!

軽~くUIElementsを使ってGUI実装してみる

 先日CustomPackageManagerというAssetを作成したのですが、そこでUIElementsを利用したGUI実装を行ってみました。最初からUSSやUXMLは難しいのでがっつりIMGUIで書いたのを最終的にVisualElementで組み立ててみました。

[SettingsProvider]
public static SettingsProvider PreferenceView()
{
    var manifestJson = FileUtility.LoadManifestJson();
    var model = new CustomPackageManagerModel( manifestJson );
    var view = new CustomPackageManagerGUI();
    var presenter = new CustomPackageManagerPresenter( model, view );

    var provider = new SettingsProvider( "Preferences/CustomPackageManager", SettingsScope.User )
    {
        label = "CustomPackageManager",
        activateHandler = ( searchContext, rootElement ) => {
   // 一番基底となるVisualElementでpaddingと要素の並べ方を指定している
            var basicContainer = new VisualElement()
            {
                style = {
                    paddingTop = 5,
                    paddingLeft = 10,
                    paddingRight = 10,
                    flexDirection = FlexDirection.Column,
                }
            };
   // タイトル表示用 別にIMGUIで実装してもよいが
            var titleElement = new VisualElement();
            titleElement.Add( new Label()
            {
                text = "CustomPackageManager",
                style =
                {
                    fontSize = 15,
                    unityFontStyleAndWeight = FontStyle.Bold,
                    flexBasis = 25,
                    minHeight = 25,
                }
            } );
   // メインのGUIのIMGUI実装
            var imguiContainer = new IMGUIContainer( () =>
            {
                view.OnIMGUI();
            } );
            imguiContainer.style.flexBasis = 1000; // ※GUILayout.FlexibleSpace()を機能させるためのもの

   // 各種パーツを登録 登録された順番に基底VisualElementが勝手に並べてくれる
            basicContainer.Add( titleElement );
            basicContainer.Add( imguiContainer );
            rootElement.Add( basicContainer );
        },
        guiHandler = ( searchText ) => {

        },
        keywords = new[] { "CustomPackageManager" }
    };
    return provider;
}

f:id:charcotte:20191009210401p:plain

上記実装でVisualElementのレイアウトはこのようになっています。ほぼIMGUIで実装されてるのでごく単純な感じです。

IMGUIをVisualElementに組み込むときの注意点

 いつものノリで実装していたら、垂直方向にGUILayout.FlexibleSpace()を利用しても全然UIに反映されない現象に遭遇しました。なんでかな~と思っていろいろと試行錯誤をしてみた結果、IMGUIContainerの描画範囲をIMGUI側で拡張することができないということがわかりました。CustomPackageManagerみたいにウインドウ全体に描画をさせたい場合、VisualElement側の大きさから設定する必要がありあそうです。実際上記コードにも書いてある通りですが、適当にVisualElement側をデカくしたらFlexibleSpaceが適用されるようになりました。

個人的なまとめ

 UIElementsを用いてUIを実装するいろんな方法を学びました。
UIElementsでUIを構築するということは、たくさんのVisualElementを自分で組み立てるということです。VisualElementそのものをスクリプト上で組み立てることもできるし、要素だけ組み立ててUSSでスタイルの指定をすることもできます。要素もスタイルもまとめてUXMLで指定することもできます。そして、従来のIMGUIで実装したものをVisualElement化して構築することも可能です。

感想

 USSとUXMLの勉強しなきゃいけないのか~って思ってたり、VisualElementって何…って困っていましたが、理解できました!
当初はUSSとかUXML使わなかったらUIElements使う意味なくない?って考えてましたが、これらを使わずとも今後のEditor拡張実装のときの強力なAPIだと感じました。
GUIを実装するときに要素を縦に並べるのも横に並べるのも、paddingやmarginもVisualElements側が持ち、各GUIパーツはほかのUIのことを全く気にする必要がなくなったように感じます。

最初は以下の記事の内容を理解するのに苦労しましたが、具体的でわかりやすい実装を見たことでよりUIElementsの世界に飛び込んでいけそうです( ˘ω˘ )
blogs.unity3d.com