⚙️
【Unity,UIToolKit】Enum選択に応じてインスペクターの入力フィールドを動的に切り替える【Custom Editor】
- UI Toolkit, Custom Editor で、
列挙型に応じて Inspector の入力フィールドを切り替える方法を解説するよ~ - 使用した Unity のバージョンは 2022.3.9f
何がしたいのか
-
float param
変数に対してインスペクターから入力ができる状況 - 選択に応じて受け付ける型を
int
,float
と切り替えたい
結論、何ができたか
- 列挙型(
mode
)に応じて、ラベルと入力できる型を動的に切り替え - あくまで入力内容は
param
へ行く
必要なプログラム
データ側
SampleData.cs
using UnityEngine;
public class SampleData : MonoBehaviour
{
public enum Mode
{
Int,
Float,
}
public Mode mode;
public float param; //mode に応じて Inspector の UI を切り替え
}
エディタ拡張側
- 今回は uGUI ではなく UIToolKit を用いている
SampleDataEditor.cs
using UnityEditor;
using UnityEditor.UIElements;
using UnityEngine;
using UnityEngine.UIElements;
[CustomEditor(typeof(SampleData))]
public class SampleDataEditor : Editor
{
SerializedProperty mode;
SerializedProperty param;
VisualElement paramContainer;
//インスペクターの UI 構築
public override VisualElement CreateInspectorGUI()
{
var root = new VisualElement();
//プロパティ取得
mode = serializedObject.FindProperty("mode");
param = serializedObject.FindProperty("param");
//モード選択フィールド
var modeField = new PropertyField(mode);
//値変更時に呼び出す関数をセット
modeField.RegisterValueChangeCallback(_ => UpdateParamField());
root.Add(modeField);
//切り替え先コンテナ
paramContainer = new VisualElement();
root.Add(paramContainer);
//バインド, 初回描画
root.Bind(serializedObject);
UpdateParamField();
return root;
}
//mode に応じて paramContainer 内を再生成する
void UpdateParamField()
{
//既存 UI を全消去し、最新値を取得する
paramContainer.Clear();
serializedObject.Update();
switch ((SampleData.Mode)mode.enumValueIndex)
{
case SampleData.Mode.Int:
var iField = new IntegerField("Int Value");
iField.value = Mathf.RoundToInt(param.floatValue);
iField.RegisterValueChangedCallback(evt => param.floatValue = evt.newValue);
paramContainer.Add(iField);
break;
case SampleData.Mode.Float:
var fField = new FloatField("Float Value");
fField.value = param.floatValue;
fField.RegisterValueChangedCallback(evt => param.floatValue = evt.newValue);
paramContainer.Add(fField);
break;
}
//SerializedProperty の変更を確定
serializedObject.ApplyModifiedProperties();
}
}
実装のポイント
-
インスペクターの操作に合わせて即時 UI 更新
modeField.RegisterValueChangeCallback(_ => UpdateParamField());
- モード変更と同時に
param
入力欄を再生成し、誤入力を防止
-
float → int へ切り替え時の丸め処理
Mathf.RoundToInt(param.floatValue)
- 前回までの小数値を整数へ変換(小数のまま
param
に残存しないようにする)
動作イメージ
補足
入力内容に他意はない
-
IntegerField
,FloatField
に文字列を入れてもちゃんと弾いてくれます
実際の開発で導入した例
背景
- サウンドライブラリでリソース(
AudioClip
)のキャッシュ機能を実装していた - LRU(Least Recently Used), TTL(Time To Live) などとキャッシュ方式毎にクラスを実装、
SoundCacheFactory
クラスで外部から指定方式のものを生成する形だった
SoundCacheFactory.cs
public static class SoundCacheFactory
{
public enum Type
{
LeastRecentlyUsed,
TimeToLive,
Random
}
/// <summary>
/// キャッシュ方式とパラメータからISoundCacheを生成する
/// </summary>
public static ISoundCache Create(Type t, float param)
{
return t switch
{
Type.LeastRecentlyUsed => CreateLRU(param),
Type.TimeToLive => CreateTTL(param),
Type.Random => CreateRandom((int)param),
_ => throw new ArgumentOutOfRangeException(nameof(t))
};
}
public static ISoundCache CreateLRU(float idleTimeThreshold) { … }
public static ISoundCache CreateTTL(float ttlSeconds) { … }
public static ISoundCache CreateRandom(int maxCacheCount) { … }
}
- また、プリセット機能としてエディタ上でのキャッシュ方式選択,
param
入力も実装していた
こんな感じ
問題
param
が何のパラメータかエディタ上ではわからない
結果
- 選択に応じてラベル(
param
の部分)と入力できる型が変わるようにした - Ramdom は
int
, それ以外はfloat
を受け付ける
撮影した時期はキャッシュ方式を一部略称で表示していた
Discussion