本記事は、サムザップ Advent Calendar 2019 #2 の12/23の記事です。
Unity2019.1新機能「UIElements」とは
IMGUIからUIElementsに置き換わるであろう新しいGUIシステムです。
UIElementsはUXML/USS/UQueryで構成されます。
所感、
UXMLはUnityで扱えるXMLベースのマークアップ言語のイメージ
USSはCSSに近い書き方でデザインが出来そう
UQueryはC#で主にロジック部分を書くのかな…
って感じでした。
Unity Blog「Unity 2019.1 の UIElements の新機能」
テキスト読んだだけじゃ頭に入ってこないので、取り敢えず触ってみよう。。
「UIElements」を触ってみる
開発環境
今回使用する環境はUnity2019.3.0f1
です。
(普段Unity2018のLTS版触っているから、Editorの見た目に違和感…w)
「UIElements」をHelloWorld
1.「UIElements」のEditorWindowを開く
右クリックでメニュー開く > [Create] > [UIElements] > [Editor Window]
もしくは
[Assets] > [Create] > [UIElements] > [Editor Window]
2.「UIElements」EditorWindowで作成
### 3.準備完了 ## 作成されたデフォルトの中身 ### DefaultUIElementsEditorWindow.cs ``` using UnityEditor; using UnityEngine; using UnityEngine.UIElements; using UnityEditor.UIElements;public class DefaultUIElementsEditorWindow : EditorWindow
{
[MenuItem("Window/UIElements/DefaultUIElementsEditorWindow")]
public static void ShowExample()
{
DefaultUIElementsEditorWindow wnd = GetWindow();
wnd.titleContent = new GUIContent("DefaultUIElementsEditorWindow");
}
public void OnEnable()
{
// Each editor window contains a root VisualElement object
VisualElement root = rootVisualElement;
// VisualElements objects can contain other VisualElement following a tree hierarchy.
VisualElement label = new Label("Hello World! From C#");
root.Add(label);
// Import UXML
var visualTree = AssetDatabase.LoadAssetAtPath<VisualTreeAsset>("Assets/Editor/DefaultUIElementsEditorWindow.uxml");
VisualElement labelFromUXML = visualTree.CloneTree();
root.Add(labelFromUXML);
// A stylesheet can be added to a VisualElement.
// The style will be applied to the VisualElement and all of its children.
var styleSheet = AssetDatabase.LoadAssetAtPath<StyleSheet>("Assets/Editor/DefaultUIElementsEditorWindow.uss");
VisualElement labelWithStyle = new Label("Hello World! With Style");
labelWithStyle.styleSheets.Add(styleSheet);
root.Add(labelWithStyle);
}
}
### DefaultUIElementsEditorWindow.uxml
<engine:UXML
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:engine="UnityEngine.UIElements"
xmlns:editor="UnityEditor.UIElements"
xsi:noNamespaceSchemaLocation="../../UIElementsSchema/UIElements.xsd"
<engine:Label text="Hello World! From UXML" />
### DefaultUIElementsEditorWindow.uss
Label {
font-size: 20px;
-unity-font-style: bold;
color: rgb(68, 138, 255);
}
# :warning: **つまづきポイント** :warning:
## 「UIElements」のEditorWindowでエラー
下記のようなエラーが発生した(何起因で発生したかは分からない…)
<img width="250" alt="CreateUIElementsWindowError.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/209889/651b95c1-dea2-220c-3a63-c554e8be6550.png">
:hospital: **対処法** :hospital:
Editorフォルダ配下で
右クリックでメニュー開く > [Update UIElements Schema]
もしくは
[Assets] > [Update UIElements Schema]
<img width="240" alt="Error.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/209889/1bef12fa-bbfe-5e08-52ae-7ef25078277c.png">
## UXMLのタグ頭に「engine:」を付けないとエラー
Unity公式のリファレンスでは「engine:」がタグ頭に付いてる例と付いていない例がある。毎回「engine:」をタグ頭につけるような冗長な書き方は避けたいけど、「engine:」を失くすとエラーになる。。
:hospital: **対処法** :hospital:
デフォルトで生成されるUXMLは下記のようにnamespaceが指定されているので、毎回「engine:」をタグ頭につける必要があったっぽい
```xmlns:engine="UnityEngine.UIElements"```
下記のようにnamespaceを失くすことで、毎回「engine:」をタグ頭に付けなくても良くなる。
※必要な時にnamespaceで区切ろうかな…
```xmlns="UnityEngine.UIElements"```
# 軽く基盤を作ってみる
## built-inのHelloWorldファイルは色々イケてない
1. UXML内に既にnamespaceが指定されている
2. USSがLabelにしか適用されない
ってことで、
とりあえず、複数のUXMLとUSSをデータとして受け取ったら、それを使ってEditorWindowを構築してくれるものが欲しい
## 試しに作ってみる
コレで、基盤のEditorWindowクラスが設定データを元に構築してくれる。
### 実際の基盤クラス
EditorWindowのデータクラス
using UnityEngine;
using UnityEngine.UIElements;
namespace System.Common.Editor.Window
{
///
/// エディタウィンドウのデータの設定クラス
///
[CreateAssetMenu(fileName = "EditorWindowSettingData", menuName = "Tools/Editor Window/Create Setting Data/EditorWindowData")]
public class EditorWindowData : ScriptableObject
{
///
/// UXML一覧
///
[SerializeField]
private VisualTreeAsset[] uxmlArray;
public VisualTreeAsset[] UxmlArray { get => uxmlArray; }
/// <summary>
/// USS一覧
/// </summary>
[SerializeField]
private StyleSheet[] ussArray;
public StyleSheet[] UssArray { get => ussArray; }
}
}
EditorWindowのBaseクラス
using UnityEditor;
using UnityEngine;
using UnityEngine.UIElements;
namespace System.Common.Editor.Window
{
///
/// エディタウィンドウのベースクラス
///
public class EditorWindowBase : EditorWindow where T : EditorWindow
{
///
/// 設定データ
///
[SerializeField]
private EditorWindowData editorSettingData;
private static T _instance;
public static T Instance { get => _instance; private set => _instance = value; }
/// <summary>
/// Window開く
/// </summary>
public static void Open()
{
Instance = GetWindow<T>();
Instance.titleContent = new GUIContent(Instance.GetType().Name);
}
/// <summary>
/// アクティブ処理
/// </summary>
protected virtual void OnEnable() { Init(); }
/// <summary>
/// 非アクティブ処理
/// </summary>
protected virtual void OnDisable() { Disable(); }
/// <summary>
/// 初期化
/// </summary>
protected virtual void Init()
{
if (editorSettingData == null)
return;
// Each editor window contains a root VisualElement object
VisualElement root = rootVisualElement;
if (root == null)
return;
// Import UXML
foreach (var uxml in editorSettingData.UxmlArray)
{
if (uxml == null)
continue;
root.Add(uxml.CloneTree());
}
// Import USS
foreach (var uss in editorSettingData.UssArray)
{
if (uss == null)
continue;
root.styleSheets.Add(uss);
}
}
/// <summary>
/// 停止処理
/// </summary>
protected virtual void Disable() { }
}
}
### テストデータ
EditorWindowの呼び出し口(TestEditorWindowを開くだけ)
using UnityEditor;
using Test.Editor.Window;
namespace Tools
{
public class ToolMenu
{
[MenuItem("Tools/Editor Window/TestWindow")]
public static void Open() { TestEditorWindow.Open(); }
}
}
EditorWindow(中身なし)
using System.Common.Editor.Window;
namespace Test.Editor.Window
{
public class TestEditorWindow : EditorWindowBase
{
}
}
UXML(中身なし)
<UXML
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="UnityEngine.UIElements"
xsi:noNamespaceSchemaLocation="../../UIElementsSchema/UIElements.xsd"
``` # 参考にしたリファレンス UIElements Developer Guide https://docs.unity3d.com/Manual/UIElements.html UXML関連 https://docs.unity3d.com/Manual/UIE-ElementRef.html
触ってみて…
IMGUIよりUIElementsの方が、Viewと処理を分けられるから読みやすそう。(小規模なツールはIMGUIの方が書きやすそうだけど…)
でも、毎回タグ頭に「engine:」をつけるのはダルいので、UXML内の対応で回避してみた。
UXMLのテンプレートを用意したり、SerializeField表示のためにBindして表示できるようにしたりと、基盤としてやらなきゃいけないことは結構あるけど、、、
って、今見たらVisualElement
にBind
がなさそう…
visualElement.Bind(serializedData);
的な感じで行けると思ったんだけど…
SerializeField表示方法をまた調べなきゃ…笑
Assetsと同階層のUIElementsSchemaディレクトリにUIElements.xsdとか色々定義されていたので、CustomしたXMLSchemaを作ってみるのをどっかで試してみたい。
明日は @m-nakayama さんの記事です。