最近のバージョンの Unity ではエディター拡張向けの色々な機能が追加され、エディター拡張がだいぶ作りやすくなりました。
エディターウィンドウの画面配置は Visual Elements
、UI Toolkit
などで UGUI のように組めて、C# のコードは処理に専念できるようになりました。
しかし、それでもエディターウィンドウは使いづらい部分はあります。
具体的に、MonoBehaviour
と比べて、エディターウィンドウは「[SerializeField]
をつけて、生成時のデフォ値を簡単で任意操作」することはできないため、簡単に初期アセットや初期数値を設定できず、少し使いづらい印象がまだ残っています。
この記事では簡単なエディターウィンドウを作りながら、上記の問題を ScriptableObject
を利用して改善する手法を紹介します。
前書き
この記事では Unity 2021.3.0f1 LTS を使用しています。
1. エディターウィンドウを作る
エディターウィンドウの制限を説明するために、最近の機能である「UI Toolkit」を使用して、簡単なエディターウィンドウを作ります。
Project ウィンドウを右クリックして、Create > UI Toolkit > Editor Window
を順に選択します。
すると3つのファイルが出てきます:
それぞれエディターウィンドウの
- UI 配置アセット(uxml)
- UI スタイルシート(uss)
- エディターの処理を書くコードファイル(cs)
です。
この記事は UI Toolkit の使い方がメインではありませんので、ここでは関連解説を省略します。
(この状態ではすでに、Window > UI ToolKit > TestWindow
で、作ったエディターウィンドウを起動できます)
生成された C# ファイルを見てみましょう。
C# ファイルを見てみると、下記のような方法で UI を表示する uxml を読み込んでいることが分かります:
なんと、UI アセットのパスがハードコートされています。
パスをハードコートをしても、ちゃんとロードできれば処理的に問題はありませんが、
パス直書きのデメリットとしては、該当アセットを移動したり 名前を変えたりすると、アセットをロードできなくなり、エディターウィンドウが壊れやすくなります。
上記のデメリットがありますので、出来ればパスをハードコートをせずに書きたいです。
2. アセットを [SerializeField] でアタッチする
この記事の最初で「EditorWindow は MonoBehaviour のように [SerializeField]
がつけて、デフォ値を設定することができない」と書いていましたが、
実はちょっとやり方と制限が違うだけで、
エディターウィンドウでも [SerializeField]
を介して、アセットをアタッチすることが可能 です。
例に、下記のコードを書いて、プロジェクトで TestWindow.cs を選んでみると、
public class TestWindow : EditorWindow
{
// uxml の C# クラス
[SerializeField] private VisualTreeAsset visualTreeAsset;
// ...
}
なんと、スクリプト自身にアタッチ可能なフィールドが表示されました。
これは「Default Reference」というもので、エディターウィンドウが Instantiate された時に、ここに設定された値が自動的に代入されます。
EditorWindow に限らず、MonoBehaviour でも同じことが可能で、Editor でエディターウィンドウや MoneBehaviour
のインスタンスの生成時に、自動的に代入されます。(※AddComponent()
など、Editor を介さずに生成したものには代入されません)
この方法を使えば、
(MonoBehaviour
のように、各 Instance で別々のデフォ値を持つことは出来ませんが、)
パス直書きの代わりにエディター上でアセットをアタッチすることが出来、ハードコードの部分を消すことが出来ました。
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");
}
}
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 デフォ値を保存しています。
[CreateAssetMenu(fileName = "TestWindowSettings", menuName = "Editor Window Settings/Test Window Settings")]
public class TestWindowSettings : ScriptableObject
{
public int num;
}
public class TestWindow : EditorWindow
{
[SerializeField] private VisualTreeAsset visualTreeAsset;
[SerializeField] private TestWindowSettings settings;
public void CreateGUI()
{
// TestWindowSettings を介して、num にアクセスできる!
var defaultNum = settings.num;
// ... 後続処理
}
}
ScriptableObject
を作ってアタッチすることで、
VisualTreeAsset のみならず、数値や画像ファイル、その他自作クラスなどでも簡単にデフォ値としてロードすることができます。
また、ScriptableObject の各値を調整して、エディターウィンドウを再度開けばすぐ反映されますので、
ハードコードのように再コンパイルの時間を待つ必要はありません。
3.1. ScriptableObject を利用した、エディターウィンドウのプリセット
ScriptableObject が大量に作れることを利用すれば、様々なデフォ値のプリセットが作れます。
使い方として、使用したい ScriptableObject プリセットを使用前にアタッチすれば、エディターウィンドウ起動時はそちらをロードするようになりますので、違うバリエーションのエディターウィンドウが起動できます。
制限
時々 Default Reference をアタッチしても、前回のキャッシュが残っていることがあるようで、
変な値が入っているのであれば、Unity Editor を再起動してみましょう。
しかし ScriptableObject を利用すれば、ScriptableObject にある値・アセットを調整すれば良くて、エディターウィンドウに追加でアタッチすることはなくなりますので、そんなに気になることはありません。
結論
エディターウィンドウは MonoBehaviour
のように柔軟にデフォ値設定ができませんが、ScriptableObject
をアタッチすれば近いことが出来ますので、エディターウィンドウの開発がだいぶ捗るようになるかと思います。
機能もりもりのエディターウィンドウを作る際に、是非上記の手法を利用してください。
それでは、良いエディター拡張ライフを!
※ 節 2 で、「生成された C# ファイルにアセットパスが直書きされている」と書いていますが、最新の Unity 2022.2 では自動的に [SerializeField] VisualTreeAsset
が付けられて、エディター上でアタッチ済みの状態で生成されます