7
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

【Unity】エディター拡張で、読み込むアセットのパスをハードコードしないために【Editor Window】

Last updated at Posted at 2022-11-03

最近のバージョンの Unity ではエディター拡張向けの色々な機能が追加され、エディター拡張がだいぶ作りやすくなりました。
エディターウィンドウの画面配置は Visual ElementsUI Toolkit などで UGUI のように組めて、C# のコードは処理に専念できるようになりました。

しかし、それでもエディターウィンドウは使いづらい部分はあります。
具体的に、MonoBehaviour と比べて、エディターウィンドウは[SerializeField] をつけて、生成時のデフォ値を簡単で任意操作」することはできないため、簡単に初期アセットや初期数値を設定できず、少し使いづらい印象がまだ残っています。

この記事では簡単なエディターウィンドウを作りながら、上記の問題を ScriptableObject を利用して改善する手法を紹介します。

前書き

この記事では Unity 2021.3.0f1 LTS を使用しています。

1. エディターウィンドウを作る

エディターウィンドウの制限を説明するために、最近の機能である「UI Toolkit」を使用して、簡単なエディターウィンドウを作ります。

Project ウィンドウを右クリックして、Create > UI Toolkit > Editor Window を順に選択します。
エディターウィンドウ作成.png

すると3つのファイルが出てきます:
ファイル.png
それぞれエディターウィンドウの

  • UI 配置アセット(uxml)
  • UI スタイルシート(uss)
  • エディターの処理を書くコードファイル(cs)

です。

この記事は UI Toolkit の使い方がメインではありませんので、ここでは関連解説を省略します。
(この状態ではすでに、Window > UI ToolKit > TestWindow で、作ったエディターウィンドウを起動できます)
 TestWindow.png


生成された C# ファイルを見てみましょう。
C# ファイルを見てみると、下記のような方法で UI を表示する uxml を読み込んでいることが分かります:

パスハードコード.png

なんと、UI アセットのパスがハードコートされています

パスをハードコートをしても、ちゃんとロードできれば処理的に問題はありませんが、
パス直書きのデメリットとしては、該当アセットを移動したり 名前を変えたりすると、アセットをロードできなくなり、エディターウィンドウが壊れやすくなります。

上記のデメリットがありますので、出来ればパスをハードコートをせずに書きたいです。

2. アセットを [SerializeField] でアタッチする

この記事の最初で「EditorWindow は MonoBehaviour のように [SerializeField] がつけて、デフォ値を設定することができない」と書いていましたが、
実はちょっとやり方と制限が違うだけで、
エディターウィンドウでも [SerializeField] を介して、アセットをアタッチすることが可能 です。

例に、下記のコードを書いて、プロジェクトで TestWindow.cs を選んでみると、

TestWindow.cs
public class TestWindow : EditorWindow
{
    // uxml の C# クラス
    [SerializeField] private VisualTreeAsset visualTreeAsset;

    // ...
}

SerializeField.png

なんと、スクリプト自身にアタッチ可能なフィールドが表示されました。

これは「Default Reference」というもので、エディターウィンドウが Instantiate された時に、ここに設定された値が自動的に代入されます。
EditorWindow に限らず、MonoBehaviour でも同じことが可能で、Editor でエディターウィンドウや MoneBehaviour のインスタンスの生成時に、自動的に代入されます。(※AddComponent() など、Editor を介さずに生成したものには代入されません)

この方法を使えば、
MonoBehaviour のように、各 Instance で別々のデフォ値を持つことは出来ませんが、)
パス直書きの代わりにエディター上でアセットをアタッチすることが出来、ハードコードの部分を消すことが出来ました。

TestWindow.cs
public class TestWindow : EditorWindow
{
    [SerializeField] private VisualTreeAsset visualTreeAsset;

    public void CreateGUI()
    {
        VisualElement root = rootVisualElement;
        // Import UXML
        // アセットパスのハードコートが不要!
        // var visualTree = AssetDatabase.LoadAssetAtPath<VisualTreeAsset>("Assets/Test/TestWindow.uxml");
        var uxml = visualTreeAsset.Instantiate();
        root.Add(uxml);
    }

    [MenuItem("Window/UI Toolkit/TestWindow")]
    public static void ShowExample()
    {
        TestWindow wnd = GetWindow<TestWindow>();
        wnd.titleContent = new GUIContent("TestWindow");
    }
}

アタッチ結果→
UIアセットアタッチ成功.png

2.1. デフォ値の制限

しかし、デフォ値の設定ができるクラスの Type に制限があります。
試しに同じ方法を倣って [SerializeField] int intField; を書いても、VisualTreeAsset のようには表示されませんでした。

どうやら デフォ値が設定できるクラスは Unity.Object から継承したクラスのみ のようで、int や通常の自作クラスなどはアタッチ出来ません。

    // 表示され、アタッチできる
    [SerializeField] private VisualTreeAsset visualTreeAsset;

    [SerializeField] private GameObject defaultPrefab;
    [SerializeField] private Sprite defaultSprite;

    // 表示されない
    [SerializeField] private int defaultIntValue;
    [SerializeField] private TestPlainClass defaultTestPlainClass; // 自作クラス

3. SciptableObject を利用して、制限を回避

Unity.Object を継承したものであれば付けられますので、
上記の「int 型 field が設定できない」問題は Unity.Object を継承している ScriptableObject を利用すれば解決できます

具体的なやり方は、[SerializeField] int を ScriptableObject に書いて、該当 ScriptableObject をエディターウィンドウにアタッチ」することです。

エディターウィンドウは ScriptableObject を介して int にアクセスできますので、
実質エディターウィンドウに int 型のデフォ値が設定できます。

下の例で、TestWindowSettings という名の ScriptableObject を作って、TestWindow で使用する予定の num デフォ値を保存しています。

TestWindowSettings.cs
[CreateAssetMenu(fileName = "TestWindowSettings", menuName = "Editor Window Settings/Test Window Settings")]
public class TestWindowSettings : ScriptableObject
{
    public int num;
}
TestWindow.cs
public class TestWindow : EditorWindow
{
    [SerializeField] private VisualTreeAsset visualTreeAsset;
    [SerializeField] private TestWindowSettings settings;

    public void CreateGUI()
    {
        // TestWindowSettings を介して、num にアクセスできる!
        var defaultNum = settings.num;

        // ... 後続処理
    }
}

ScriptableObjectを介して色々をシリアライズする.png

ScriptableObject を作ってアタッチすることで、
VisualTreeAsset のみならず、数値や画像ファイル、その他自作クラスなどでも簡単にデフォ値としてロードすることができます。

また、ScriptableObject の各値を調整して、エディターウィンドウを再度開けばすぐ反映されますので、
ハードコードのように再コンパイルの時間を待つ必要はありません。

3.1. ScriptableObject を利用した、エディターウィンドウのプリセット

ScriptableObject が大量に作れることを利用すれば、様々なデフォ値のプリセットが作れます。

使い方として、使用したい ScriptableObject プリセットを使用前にアタッチすれば、エディターウィンドウ起動時はそちらをロードするようになりますので、違うバリエーションのエディターウィンドウが起動できます。

制限

時々 Default Reference をアタッチしても、前回のキャッシュが残っていることがあるようで、
変な値が入っているのであれば、Unity Editor を再起動してみましょう。

しかし ScriptableObject を利用すれば、ScriptableObject にある値・アセットを調整すれば良くて、エディターウィンドウに追加でアタッチすることはなくなりますので、そんなに気になることはありません。

結論

エディターウィンドウは MonoBehaviour のように柔軟にデフォ値設定ができませんが、ScriptableObject をアタッチすれば近いことが出来ますので、エディターウィンドウの開発がだいぶ捗るようになるかと思います。
機能もりもりのエディターウィンドウを作る際に、是非上記の手法を利用してください。

それでは、良いエディター拡張ライフを!

 
※ 節 2 で、「生成された C# ファイルにアセットパスが直書きされている」と書いていますが、最新の Unity 2022.2 では自動的に [SerializeField] VisualTreeAsset が付けられて、エディター上でアタッチ済みの状態で生成されます

7
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?