Unity は MonoBehaviours と ScriptableObject に対してデフォルトのインスペクターを生成しますが、カスタムインスペクターの作成には、次のような理由があります。
UI Toolkit を使用したカスタムインスペクターの作成は、IMGUI (Immediate Mode GUI) と似ていますが、UI Toolkit には自動データバインディングや自動の “元に戻す” サポートなど、いくつかの利点があります。IMGUI がインスペクターの UI をすべてスクリプトで作成するのに対し、UI Toolkit ではスクリプト、UI Builder で視覚的な方法、またはその両方の組み合わせで UI を作成することができます。
このガイドの最終的なソースコードは、このページの 下の方 にあります。
このガイドでは、MonoBehaviour クラスのカスタムインスペクターを作成します。スクリプトと UXML (UI Builder を使用) の両方を使用して、UI を作成します。また、カスタムインスペクターには、カスタムプロパティのドローワーも用意されています。
このガイドは、Unity を使い慣れていて、UI Toolkit を使い慣れてはいない開発者のためのものです。Unity と C# スクリプティングについて基本的な知識があることが推奨されます。
このガイドでは、以下の概念も参照しています。
以下のトピックを説明します。
このガイドでは、以下を行います。
始めに、カスタムインスペクターを作成するために、MonoBehaviour
か ScriptableObject
のいずれかのカスタムクラスを作成する必要があります。このガイドでは、モデルや色などのプロパティを持つ単純な車を表す MonoBehaviour
スクリプトを使います。
Assets/Scripts 内に新しいスクリプトファイル Car.cs
を作成し、以下のコードをコピーしてください。
using UnityEngine;
public class Car : MonoBehaviour
{
public string m_Make = "Toyota";
public int m_YearBuilt = 1980;
public Color m_Color = Color.black;
}
シーン内に新しいゲームオブジェクトを作成し、Car
スクリプトコンポーネントをそれにアタッチします。
任意のシリアライズされたオブジェクトのカスタムインスペクターを作成するには、Editor 基本クラスから派生したクラスを作成し、CustomEditor 属性をそのクラスに加える必要があります。この属性によって、Unity はこのカスタムインスペクターがどのクラスを表しているのかを知ることができます。UI Toolkit でのこのワークフローは、Immediate Mode GUI (IMGUI) と同じです。
Assets/Scripts/Editor 内に Car_Inspector.cs
ファイルを作成し、それに以下のコードをコピーします。
using UnityEditor;
using UnityEditor.UIElements;
using UnityEngine.UIElements;
[CustomEditor(typeof(Car))]
public class Car_Inspector : Editor
{
}
ノート |
---|
カスタムインスペクターファイルは Editor フォルダー内、または Editor-only アセンブリ定義内になければなりません。スタンドアロンビルドを作成しようとすると、UnityEditor 名前空間が利用できないため失敗します。 |
この時点で Car
コンポーネントを持つゲームオブジェクトを選択すると、Unity はデフォルトのインスペクターを表示したままです。Car_Inspector
クラス内の CreateInspectorGUI() をオーバーライドして、デフォルトのインスペクターを置き換える必要があります。
CreateInspectorGUI()
関数は、インスペクターのビジュアルツリーを構築します。この関数は、UI を含む VisualElement を返す必要があります。下の CreateInspectorGUI()
の実装では、新しい VisualElement を作成し、それにラベルを加えます。
Car_Inspector
スクリプト内の CreateInspectorGUI()
関数をオーバーライドし、それに以下のコードをコピーします。
public override VisualElement CreateInspectorGUI()
{
// インスペクター UI のルートとなる新しい VisualElement を作成します。
VisualElement myInspector = new VisualElement();
// 簡単なラベルを加えます。
myInspector.Add(new Label("This is a custom inspector"));
// インスペクター UI を返します。
return myInspector;
}
UI Toolkit では、2 つの方法で UI コントロールを加えることができます。
このセクションでは、UI Builder を使用して UI を含む UXML ファイルを作成し、コードを使って UXML ファイルから UI をロードしてインスタンス化します。
メニュー Window > UI Toolkit > UI Builder で UI Builder を開き、UI Builder のメニュー File > New を使って、新しい Visual Tree アセットを作成し ます。
UI Toolkit を使用してエディターウィンドウやカスタムインスペクターを作成する場合、追加のコントロールタイプが提供されます。デフォルトでは、これらのエディター専用コントロールは UI Builder では表示されません。これらを利用できるようにするには、チェックボックス Editor Extension Authoring を有効にする必要があります。
Hierarchy ビューで <unsaved file>*.uxml
を選択し、Editor Extension Authoring のチェックボックスを有効にします。
ノート |
---|
UI Toolkit を使用してエディターウィンドウとカスタムインスペクターを作成する場合、Project Settings > UI Builder でこの設定をデフォルトで有効にすることができます。 |
UI にコントロールを追加するには、 Library からコントロールを選択し、上記の Hierarchy にドラッグします。自動レイアウトを変更しない限り、新しいコントロールの位置や大きさを調整する必要はありません。デフォルトでは、ラベルは利用可能なパネルの幅全体を使用し、高さは選択されたフォントサイズに調整されます。
ラベルコントロールを Library から Hierarchy にドラッグして、ビジュアルツリーに加えます。
ラベル内のテキストを変更するには、ラベルを選択し、UI Builder エディターの右側にある要素のインスペクターでテキストを変更します。
UI Builder がビジュアルツリーを保存する場合、Visual Tree アセット として UXML 形式で保存されます。これについては、UXML ドキュメント を参照してください。
下の UXML は、これまでの手順で UI Builder が生成したコードを表示したものです。
<ui:UXML xmlns:ui="UnityEngine.UIElements" xmlns:uie="UnityEditor.UIElements" editor-extension-mode="True">
<ui:Label text="Label created in UI Builder" />
</ui:UXML>
Asset > Script > Editor で作成したビジュアルツリーを、UI Builder の File メニューを使用して Car_Inspector_UXML.uxml
として保存します。
作成した UXML ファイルをカスタムインスペクター内で使用するには、CreateInspectorGUI()
関数内でロードして複製し、ビジュアルツリーに加える必要があります。これを行うには、CloneTree メソッドを使用します。作成された要素の親として動作するように、任意の VisualElement
をパラメーターとして渡すことができます。
CreateInspectorGUI()
関数を修正して UXML ファイル内のビジュアルツリーを複製し、カスタムインスペクターで使用します。
public override VisualElement CreateInspectorGUI()
{
// インスペクター UI のルートとなる新しい VisualElement を作成します。
VisualElement myInspector = new VisualElement();
// 簡単なラベルを加えます。
myInspector.Add(new Label("This is a custom inspector"));
// UXML のビジュアルツリーをロードして複製します。
VisualTreeAsset visualTree = AssetDatabase.LoadAssetAtPath<VisualTreeAsset>("Assets/Scripts/Editor/Car_Inspector_UXML.uxml");
visualTree.CloneTree(myInspector);
// インスペクター UI を返します。
return myInspector;
}
car
コンポーネントのインスペクターに、スクリプトによるものと UI Builder/UXML によるものの 2 つの作成済みラベルが表示されます。
このコードでは、ビジュアルツリーを複製するために、Visual Tree アセット (UXML) ファイルをロードする必要があり、ハードコードされたパスとファイル名を使用します。ただし、ハードコードされたファイルは推奨されません。なぜなら、ファイルのパスや名前など、ファイルのプロパティが変更されると、コードが無効になる可能性があるためです。
Visual Tree アセットにアクセスするためのより良い解決策は、アセットファイルへの参照を使用することです。メタファイル内の GUID は、ファイルの参照を保存します。ファイル名を変更したり移動したりしても GUID は変更されず、Unity はその新しい場所からファイルを見つけてロードすることができます。
プレハブと ScriptableObject の場合、エディターで他のファイルへの参照を割り当てることができます。スクリプトファイルの場合、Unity では、Default Reference
を設定することができます。 window クラスの VisualTreeAsset
型のパブリックフィールドを宣言すると、Inspector では、対応するオブジェクトフィールドに参照をドラッグする機能が可能です。これは、Car_Inspector
クラスの新しいインスタンスには、対応する VisualTreeAsset
オブジェクトへの参照が設定されることを意味します。この方法は、カスタムインスペクターやエディターウィンドウのスクリプトに UXML ファイルを割り当てるために推奨される方法です。
スクリプト内に VisualTreeAsset
のパブリック変数を作成し、Car_Inspector_UXML.uxml
ファイルをエディターのデフォルト参照として割り当てます。
public VisualTreeAsset m_InspectorXML;
ノート |
---|
デフォルトの参照はエディターでのみ機能します。AddComponent() メソッドを使用したスタンドアロンビルドのランタイムコンポーネントでは機能しません。 |
デフォルトの参照を使用すると、LoadAssetAtPath
関数を使用して VisualTreeAsset
をロードする必要がなくなります。その代わりに、CloneTree を UXML ファイルへの参照に直接使用することができます。
これにより、 CreateInspectorGUI()
メソッド内のコードを 3 行に減らすことができます。
public VisualTreeAsset m_InspectorXML;
public override VisualElement CreateInspectorGUI()
{
// インスペクター UI のルートとなる、新しい VisualElement を作成します。
VisualElement myInspector = new VisualElement();
// デフォルトの参照からロードします
m_InspectorXML.CloneTree(myInspector);
// インスペクターの UI を返します
return myInspector;
}
このカスタムインスペクターの目的は、Car
クラスのすべてのプロパティを表示することです。ユーザーが UI コントロールのいずれかを変更すると、Car
クラスのインスタンス内の値も変更される必要があります。そのためには、ビジュアルツリーに UI コントロールを加えて、クラスの個々のプロパティに接続する必要があります。
UI Toolkit supports linking of UI controls to serialized properties with data binding. Controls that are bound to a serialized property automatically display the current value of a property, and they also automatically update the property value if the user changes it in the UI. You don’t have to write code that retrieves a value from a control and writes it back to the property, like you would in IMGUI.
UI Builder を使って、車の m_Make
プロパティの TextField
コントロールをインスペクターに加えます。
コントロールとシリアライズされたプロパティを結びつけるには、そのプロパティをコントロールの binding-path
フィールドに割り当てます。この作業は、コード、UXML、UI Builder で行うことができます。プロパティは、名前によって一致します。そのため、綴りを確認するようにしてください。
新しい TextField
を、UI Builder の m_Make
プロパティにバインドします。
下は、インスペクター UI の UXML コードで、データバインディング属性も含みます。
<ui:UXML xmlns:ui="UnityEngine.UIElements" xmlns:uie="UnityEditor.UIElements" editor-extension-mode="True">
<ui:TextField label="Make of the car" text="<not set>" binding-path="m_Make" />
</ui:UXML>
コントロールのバインディングパスを設定するとき、コントロールにリンクすべきシリアル化されたプロパティの名前を伝えます。しかし、コントロールは、そのプロパティが属するシリアル化されたオブジェクトのインスタンスも受け取る必要があります。VisualElement.Bind メソッドを使用して、 MonoBehaviour
などのシリアル化されたオブジェクトを Visual Tree 全体にバインドすることができ、個々のコントロールはそのオブジェクト上の適切なプロパティにバインドされます。
When writing a custom inspector, binding is automatic. CreateInspectorGUI()
does an implicit bind after you return your visual tree. To learn more, see the documentation page on Data binding.
UI Toolkit はシリアル化されたプロパティを使うので、Undo/Redo (元に戻す/やり直し) 機能をサポートするために追加のコードは必要ありません。自動的にサポートされます。
Car
クラスのプロパティを表示するには、各フィールドのコントロールを追加する必要があります。コントロールは、プロパティのタイプと一致するため、バインドすることができます。例えば、int
は Integer フィールドまたは Integer スライダーにバインドされます。
プロパティタイプに基づく特定のコントロールを追加する代わりに、汎用的な PropertyField コントロールを利用することも可能です。このコントロールは、ほとんどのタイプのシリアル化されたプロパティで動作し、このプロパティタイプのデフォルトインスペクター UI を生成します。
PropertyField
コントロールを、Car
クラスの m_YearBuilt
と m_Color
プロパティに加えます。それぞれにバインドパスを割り当て、Label
のテキストを入力します。
PropertyField
の利点は、スクリプト内部で変数の型を変更すると、インスペクターの UI が自動的に調整されることです。ただし、ビジュアルツリーがシリアライズされたオブジェクトにバインドされ、UI Toolkit がプロパティタイプを決定するまで必要なコントロールタイプは不明なので、UI Builder 内でコントロールのプレビューを取得することはできません。
カスタムプロパティドローワーは、カスタムの serializable クラスのためのカスタムインスペクター UI です。その serializable クラスが他のシリアライズされたオブジェクトの一部である場合、カスタム UI はインスペクターにそのプロパティを表示します。UI Toolkit では、PropertyField
コントロールは、フィールドのカスタムプロパティドローワーが存在する場合、それを表示します。
Assets/Scripts 内に新しいスクリプトファイル Tire.cs
を作成し、それに以下のコードをコピーしてください。
[System.Serializable]
public class Tire
{
public float m_AirPressure = 21.5f;
public int m_ProfileDepth = 4;
}
以下のコードのように、 Car
クラスに Tire
のリストを追加します。
public class Car : MonoBehaviour
{
public string m_Make = "Toyota";
public int m_YearBuilt = 1980;
public Color m_Color = Color.black;
// この車には 4 つのタイヤがあります
public Tire[] m_Tires = new Tire[4];
}
PropertyField
コントロールは、すべての標準的なプロパティタイプで動作しますが、カスタムのシリアライズ可能なクラスと配列もサポートします。車のタイヤのプロパティを表示するには、UI Builder で別の PropertyField
を追加して m_Tires
にバインドします。
m_Tires
プロパティの PropertyField
コントロールを加えます。
現在のインスペクター UI 用に生成された Car_Inspector_UXML.uxml
の UXML コードは以下のとおりです。
<ui:UXML xmlns:ui="UnityEngine.UIElements" xmlns:uie="UnityEditor.UIElements" editor-extension-mode="True">
<ui:TextField label="Make of the car" text="<not set>" binding-path="m_Make" />
<uie:PropertyField label="Year Built" binding-path="m_YearBuilt" />
<uie:PropertyField binding-path="m_Color" label="Paint Color" />
<uie:PropertyField binding-path="m_Tires" label="Tires" />
</ui:UXML>
カスタムプロパティードローワーを使うと、リスト内の個々の Tire
(タイヤ) 要素の外観をカスタマイズすることができます。カスタムプロパティドローワーは Editor
基礎クラスから派生するのではなく、PropertyDrawer クラスから派生します。カスタムプロパティの UI を作成するには、CreatePropertyGUI メソッドをオーバーライドする必要があります。
フォルダー Assets/Scripts/Editor 内に新しいスクリプト Tire_PropertyDrawer.cs
を作成し、その中に以下のコードをコピーします。
using UnityEditor;
using UnityEditor.UIElements;
using UnityEngine.UIElements;
[CustomPropertyDrawer(typeof(Tire))]
public class Tire_PropertyDrawer : PropertyDrawer
{
public override VisualElement CreatePropertyGUI(SerializedProperty property)
{
// プロパティ UI のルートとなる新しい VisualElement を作成します。
var container = new VisualElement();
// C# を使用してドロワー UI を作成します。
// ...
// UI を返します。
return container;
}
}
カスタマイズされたインスペクターのように、コードと UXML を使用してプロパティの UI を作成することができます。この例では、コードを使用してカスタム UI を作成します。
CreatePropertyGUI
メソッドを以下のように拡張して、Tire
クラスのプロパティドローワー用のカスタム UI を作成します。
public override VisualElement CreatePropertyGUI(SerializedProperty property)
{
// プロパティ UI のルートとなる新しい VisualElement を作成します。
var container = new VisualElement();
// C# を使用してドロワー UI を作成します。
var popup = new UnityEngine.UIElements.PopupWindow();
popup.text = "Tire Details";
popup.Add(new PropertyField(property.FindPropertyRelative("m_AirPressure"), "Air Pressure (psi)"));
popup.Add(new PropertyField(property.FindPropertyRelative("m_ProfileDepth"), "Profile Depth (mm)"));
container.Add(popup);
// UI を返します。
return container;
}
For more information on property drawers, please see the documentation of PropertyDrawer
カスタムインスペクターを開発する際、デフォルトインスペクターへのアクセスを保っておくと便利です。UI Toolkit を使えば、デフォルトのインスペクターの UI をカスタム UI に簡単に加えることができます。
UI Builder で UI に Foldout
(折りたたみ) コントロールを追加し Default_Inspector と名付け、ラベルテキストを割り当てます。
UI Builder を使用して折りたたみを作成しますが、インスペクターは作成しません。デフォルトのインスペクターのコンテンツは、インスペクタースクリプトの内部で生成され、コードを通して折りたたみコントロールにアタッチされます。
UI Builder で作成した折りたたみにデフォルトのインスペクター UI を添付するには、折りたたみへの参照を取得する必要があります。インスペクターのビジュアルツリーから、折りたたみのビジュアル要素を取得できます。これは、API の UQuery ファミリーを使用して行います。UI 内の個々の要素は、名前、USS クラス、タイプ、またはこれらの属性の組み合わせで取得できます。
Foldout
コントロールへの参照を、 CreateInspectorGUI
メソッド内で、UI Builder で設定した名前を使用して取得します。
// デフォルトインスペクターの折りたたみコントロールへの参照を取得します
VisualElement inspectorFoldout = myInspector.Q("Default_Inspector");
The FillDefaultInspector method of the InspectorElement creates a visual tree with a default inspector for a given serialized object and attaches it to the parent visual element passed into the method as a parameter.
以下のコードでデフォルトインスペクターを作成し、折りたたみ (Foldout) にアタッチします。
// デフォルトインスペクターを Foldout (折りたたみ) にアタッチします
InspectorElement.FillDefaultInspector(inspectorFoldout, serializedObject, this);
以下に、このガイドで作成したすべてのファイルの完全なソースコードを掲載します。
using UnityEngine;
public class Car : MonoBehaviour
{
public string m_Make = "Toyota";
public int m_YearBuilt = 1980;
public Color m_Color = Color.black;
// この車には 4 つのタイヤがあります
public Tire[] m_Tires = new Tire[4];
}
using UnityEditor;
using UnityEditor.UIElements;
using UnityEngine.UIElements;
[CustomEditor(typeof(Car))]
public class Car_Inspector : Editor
{
public VisualTreeAsset m_InspectorXML;
public override VisualElement CreateInspectorGUI()
{
// インスペクター UI のルートとなる、新しい VisualElement を作成します
VisualElement myInspector = new VisualElement();
// デフォルトの参照からロードします
m_InspectorXML.CloneTree(myInspector);
// デフォルトインスペクターの折りたたみコントロールへの参照を取得します
VisualElement inspectorFoldout = myInspector.Q("Default_Inspector");
// デフォルトインスペクターを Foldout にアタッチします
InspectorElement.FillDefaultInspector(inspectorFoldout, serializedObject, this);
// インスペクターの UI を返します
return myInspector;
}
}
<ui:UXML xmlns:ui="UnityEngine.UIElements" xmlns:uie="UnityEditor.UIElements" editor-extension-mode="True">
<ui:TextField label="Make of the car" text="<not set>" binding-path="m_Make" />
<uie:PropertyField label="Year Built" binding-path="m_YearBuilt" />
<uie:PropertyField binding-path="m_Color" label="Paint Color" />
<uie:PropertyField binding-path="m_Tires" label="Tires" />
<ui:Foldout text="Default Inspector" name="Default_Inspector" />
</ui:UXML>
[System.Serializable]
public class Tire
{
public float m_AirPressure = 21.5f;
public int m_ProfileDepth = 4;
}
using UnityEditor;
using UnityEditor.UIElements;
using UnityEngine.UIElements;
[CustomPropertyDrawer(typeof(Tire))]
public class Tire_PropertyDrawer : PropertyDrawer
{
public override VisualElement CreatePropertyGUI(SerializedProperty property)
{
//プロパティ UI のルートとなる新しい VisualElement を作成します
var container = new VisualElement();
// C# を使用してドロワー UI を作成します
var popup = new UnityEngine.UIElements.PopupWindow();
popup.text = "Tire Details";
popup.Add(new PropertyField(property.FindPropertyRelative("m_AirPressure"), "Air Pressure (psi)"));
popup.Add(new PropertyField(property.FindPropertyRelative("m_ProfileDepth"), "Profile Depth (mm)"));
container.Add(popup);
// UI を返します
return container;
}
}