🛠️

[Unity] UI Elements の UXML, USS の指定をパス移動に強くする

2021/01/20に公開

【追記】
Unity 2022.3 時点でコンテキストメニューから作成した場合、同様の処理が行われるようになっています。スクリプトとUXMLを別で作成したときなどは参考にしてみてください。

概要

Unity のエディタ拡張を書くときに使用する UI Elements にて、uxml, uss のパス指定を GUID 経由にしてファイルの移動やリネームに強くするお話です。

UI Elements のサンプルではパス指定でロードしている

UI Elements のサンプルだとだいたい下記のような形でパスを指定して AssetDatabase 経由でロードする形になっています。

public class MyWindow : EditorWindow
{
    private const string UxmlPath = "Assets/Editor/MyWindow.uxml";
    private const string UssPath =  "Assets/Editor/MyWindow.uss";
    
    protected void OnEnable()
    {
        var template = AssetDatabase.LoadAssetAtPath<VisualTreeAsset>(UxmlPath);
        template.CloneTree(rootVisualElement);

        var styleSheet = AssetDatabase.LoadAssetAtPath<StyleSheet>(UssPath);
        rootVisualElement.styleSheets.Add(styleSheet);
    }
}

簡単ですね。これで問題無く読み込みは出来るようになります。
ただ、開発を進めていく段階で少し面倒なことが起こってくる可能性があります。

ファイルの場所を移動するとソースコードを修正する必要がある

たとえば、今は Assets/Editor/ に置いている uxml, uss を Assets/Editor/Hoge/ の下に移動する事になった場合、下記のようにソースコードを書き換える必要があります。

public class MyWindow : EditorWindow
{
-    private const string UxmlPath = "Assets/Editor/MyWindow.uxml";
-    private const string UssPath =  "Assets/Editor/MyWindow.uss";
+    private const string UxmlPath = "Assets/Editor/Hoge/MyWindow.uxml";
+    private const string UssPath =  "Assets/Editor/Hoge/MyWindow.uss";

パスがソースに埋め込まれているため、ファイル移動したタイミングで修正を忘れやすいです。忘れてしまうと実行した後にファイルを読み込めないエラーが出てげんなりしてしまいます。

これはもちろんファイル名のリネームによっても発生します。

Seriralize Field 経由で参照することでパス移動に強くする

じゃあ、どうするか。
パスの指定をソースコード内で行っていることで発生するのであればそれを無くせばいい。
そう、いつもUnityでお世話になっているアレです。

Serialize Field を作成して、Inspector へドラッグ&ドロップです。

Serialize Field の作成

早速 Serialize Field 経由で設定できるようにコードを修正しましょう。

public class MyWindow : EditorWindow
{
    public VisualTreeAsset visualTreeTemplate;
    public StyleSheet styleSheet;
    
    protected void OnEnable()
    {
        visualTreeTemplate.CloneTree(rootVisualElement);
        rootVisualElement.styleSheets.Add(styleSheet);
    }
}

パスを使用した AssetDatabase経由のロードを止めて MonoBehaviour などと同様に Serialize Field を経由して取得するようにしました。
かなりシンプルになりましたね。

でも、いつもの MonoBehaviour だと Prefab や Scene から参照を設定しています。
今回はどうやって参照を設定するんでしょう?

Reference の設定

Reference の設定は MyWindow.cs 自体を選択したときの Inspector 上から行います。
下記を参照してください。

これで、EditorWindow が Open されたときには以前のようにパス指定で読み込まなくてもしっかりインスタンスが設定されています。

便利!

これなら UXML や USS ファイルを移動したりリネームしてもしっかり読み込んでくれます。

どうなってるの?

Serialize Field って普通はシーンやプレハブに保存する物ですよね。
スクリプトに直接 Serialize しているというのはどういう状況なのか。

下記が設定されたときの MyWindow.cs.meta の内容です。
defaultReferences という項目にGUIDが設定されています。
ここに保存されていて、Windowが開くときに Unity が設定して開いてくれるということです。

fileFormatVersion: 2
guid: 957c9efbf85045c8b717564a28fb38d3
MonoImporter:
  externalObjects: {}
  serializedVersion: 2
  defaultReferences:
  - m_ViewDataDictionary: {instanceID: 0}
  - visualTreeTemplate: {fileID: 9197481963319205126, guid: a01b586d170f9446eaf1f5a3fa03240e,
      type: 3}
  - styleSheet: {fileID: 7433441132597879392, guid: 298ffae2bc2de45abbb293e822b8eb95,
      type: 3}
  executionOrder: 0
  icon: {instanceID: 0}
  userData: 
  assetBundleName: 
  assetBundleVariant: 

ただし・・ null になることがある?

私も原因と対策がはっきりしていないのですが、下記のようなスクリプトコンパイルが走った直後などは Serialize Field の復旧が上手くいかずに null が返ってくることがある模様です。

  1. 上記の手順で作成したWindowを開いた状態にする
  2. スクリプトを修正したりしてコンパイルが走る
  3. コンパイル終了後に Domain Reload が起こり、OnEnable が呼ばれる
  4. このときに null が返ってくることがある

原因と対策がつかめていないため、読み込めなかった場合は Refresh ボタンを表示して Reload を行いお茶を濁しています。なにか良い対策などがありましたら教えてください。

以上です。

Discussion