4.8K Views
April 07, 25
スライド概要
EnumとTextFieldに属性をつけるだけでTextFieldをプルダウンにします。PropertyDrawerを使った拡張事例をご紹介します。
登壇者 : りっく / 株式会社ディー・エヌ・エー
---------
Unity エディタ拡張 完全に理解した 勉強会
https://unity-fully-understood.connpass.com/event/347805/
動画アーカイブ : https://www.youtube.com/watch?v=9twjUwKQ-4M
DeNA が社会の技術向上に貢献するため、業務で得た知見を積極的に外部に発信する、DeNA 公式のアカウントです。DeNA エンジニアの登壇資料をお届けします。
もう入力ミスはしない! エディタ拡張で実現するTextFieldのプルダウン化 高橋陸登(りっく) 株式会社ディー・エヌ・エー Unity エディタ拡張完全に理解した 勉強会 2025/04/04 © DeNA Co., Ltd. 1
自己紹介 高橋 陸登 株式会社ディー・エヌ・エー ・2018年4月DeNA入社 ・クライアントエンジニア ・IPタイトルの開発を経て 現在はプロトタイプの開発などをしています ・ツール開発も好きなので興味がある方 ぜひ一緒にお話ししましょう © DeNA Co., Ltd. 2
1 最終的なゴール ● 属性をつけるだけで拡張可能 ● Enumの値をプルダウンにしている ● stringでの自由入力も可能 ● stringでSerializeされる ※発表は動画でした 動画はイベントアーカイブをご覧ください © DeNA Co., Ltd. 3
1 なんでstringをプルダウンにしたい? ● 入力ミスを防ぎたい ● 非エンジニアの人が使いやすいようにしたい ● stringをいちいち入力するのってめんどくさくないですか? © DeNA Co., Ltd. 4
じゃあ作ってみよう 1 public enum SampleType { Hoge, Fuga, Piyo } public class StringToEnumAttribute : PropertyAttribute { public readonly Type Type; public StringToEnumAttribute(Type type) { Type = type; } } public class StringToEnumSample : MonoBehaviour { [SerializeField] [StringToEnum(typeof(SampleType))] private string _label1 = SampleType.Hoge.ToString(); [SerializeField] [StringToEnum(typeof(SampleType))] private string _label2 = SampleType.Hoge.ToString(); [SerializeField] [StringToEnum(typeof(SampleType))] private string _label3 = SampleType.Hoge.ToString(); } © DeNA Co., Ltd. 5
じゃあ作ってみよう 1 public enum SampleType { Hoge, Fuga, プルダウンにしたいEnumを定義 Piyo } public class StringToEnumAttribute : PropertyAttribute { public readonly Type Type; public StringToEnumAttribute(Type type) { Type = type; } } public class StringToEnumSample : MonoBehaviour { [SerializeField] [StringToEnum(typeof(SampleType))] private string _label1 = SampleType.Hoge.ToString(); [SerializeField] [StringToEnum(typeof(SampleType))] private string _label2 = SampleType.Hoge.ToString(); [SerializeField] [StringToEnum(typeof(SampleType))] private string _label3 = SampleType.Hoge.ToString(); } © DeNA Co., Ltd. 6
じゃあ作ってみよう 1 public enum SampleType { Hoge, Fuga, Piyo } public class StringToEnumAttribute : PropertyAttribute { public readonly Type Type; StringからEnumに変換するAttributeを定義 public StringToEnumAttribute(Type type) { Type = type; } } public class StringToEnumSample : MonoBehaviour { [SerializeField] [StringToEnum(typeof(SampleType))] private string _label1 = SampleType.Hoge.ToString(); [SerializeField] [StringToEnum(typeof(SampleType))] private string _label2 = SampleType.Hoge.ToString(); [SerializeField] [StringToEnum(typeof(SampleType))] private string _label3 = SampleType.Hoge.ToString(); } © DeNA Co., Ltd. 7
じゃあ作ってみよう 1 public enum SampleType { Hoge, Fuga, Piyo } public class StringToEnumAttribute : PropertyAttribute { public readonly Type Type; コンポーネント側のTextFieldに public StringToEnumAttribute(Type type) { Type = type; StringToEnumAttributeをつけて引数にSampleTypeを渡す } } public class StringToEnumSample : MonoBehaviour { [SerializeField] [StringToEnum(typeof(SampleType))] private string _label1 = SampleType.Hoge.ToString(); [SerializeField] [StringToEnum(typeof(SampleType))] private string _label2 = SampleType.Hoge.ToString(); [SerializeField] [StringToEnum(typeof(SampleType))] private string _label3 = SampleType.Hoge.ToString(); } © DeNA Co., Ltd. 8
じゃあ作ってみよう
1
[CustomPropertyDrawer(typeof(StringToEnumAttribute))]
public class StringToEnumPropertyDrawer : PropertyDrawer
{
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
var attr = (StringToEnumAttribute)attribute;
if (property.propertyType != SerializedPropertyType.String)
{
return;
}
var prevEnumValue = Enum.Parse(attr.Type, property.stringValue, true);
var nextEnumValue = EditorGUI.EnumPopup(position, label, (Enum)prevEnumValue);
property.stringValue = nextEnumValue.ToString();
}
}
© DeNA Co., Ltd.
9
じゃあ作ってみよう
1
[CustomPropertyDrawer(typeof(StringToEnumAttribute))]
public class StringToEnumPropertyDrawer : PropertyDrawer
{
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
var attr = (StringToEnumAttribute)attribute;
if (property.propertyType != SerializedPropertyType.String)
{
return;
}
var prevEnumValue = Enum.Parse(attr.Type, property.stringValue, true);
var nextEnumValue = EditorGUI.EnumPopup(position, label, (Enum)prevEnumValue);
property.stringValue = nextEnumValue.ToString();
StringをEnumにパースし
EditorGUI.EnumPopupで表示する
}
}
© DeNA Co., Ltd.
10
1 © DeNA Co., Ltd. じゃあ作ってみよう 11
これをもっと汎用的にできないか © DeNA Co., Ltd. 12
開発環境 1 ● この機能を汎用的にしてライブラリ側でも使えるようにして欲しいという要望がでた ○ 具体的には汎用ボタンのオーディオ再生コンポーネントの オーディオ指定を文字列ではなくプルダウンにして欲しいとのこと ● 内部的にenumで扱いづらい事情がある ○ 弊社のオーディオライブラリでは AudioClipをまとめたScriptableObjectがあり Key Value形式で格納しているためstringのkeyで指定する必要がある © DeNA Co., Ltd. 13
課題 1 ● ボタンの汎用コンポーネントはライブラリ側に実装されている ● Enumの定義はプロダクト毎に定義したい →先ほどの方法だとコンポーネント側にEnumを指定する必要がある →テキストフィールドを抽象化して汎用ボタンを継承する方法もあるが冗長 さてどうする・・・? © DeNA Co., Ltd. 14
属性を別の型に依存させてみる © DeNA Co., Ltd. 15
属性を別の型に依存させてみる 1 public class LabelAttribute : PropertyAttribute { public Type Tag { get; } public LabelAttribute(Type tag) { Tag = tag; } } [AttributeUsage(AttributeTargets.Enum, AllowMultiple = true)] public class EnumLabelAttribute : Attribute { public Type Tag { get; } public EnumLabelAttribute(Type tag) { Tag = tag; } } © DeNA Co., Ltd. 16
属性を別の型に依存させてみる 1 public class LabelAttribute : PropertyAttribute { public Type Tag { get; } コンポーネント側で利用する public LabelAttribute(Type tag) LabelAttributeを定義 { Tag = tag; } System.TypeのTagを引数として持つ } [AttributeUsage(AttributeTargets.Enum, AllowMultiple = true)] public class EnumLabelAttribute : Attribute { public Type Tag { get; } public EnumLabelAttribute(Type tag) { Tag = tag; } } © DeNA Co., Ltd. 17
属性を別の型に依存させてみる 1 public class LabelAttribute : PropertyAttribute { public Type Tag { get; } public LabelAttribute(Type tag) { Tag = tag; } } [AttributeUsage(AttributeTargets.Enum, AllowMultiple = true)] public class EnumLabelAttribute : Attribute { public Type Tag { get; } public EnumLabelAttribute(Type tag) Enum側で利用するEnumLabelAttributeを定義 System.TypeのTagを引数として持つ { Tag = tag; } } © DeNA Co., Ltd. 18
属性を別の型に依存させてみる 1 public class LabelSample : MonoBehaviour { public class Tag1 { } Tag1, Tag2, Tag3を定義 public class Tag2 これらはTextFieldに対応している { } System.Typeであれば実際なんでも良いです public class Tag3 { } [SerializeField] [Label(typeof(Tag1))] private string _label1; [SerializeField] [Label(typeof(Tag2))] private string _label2; [SerializeField] [Label(typeof(Tag3))] private string _label3; } © DeNA Co., Ltd. 19
属性を別の型に依存させてみる 1 public class LabelSample : MonoBehaviour { public class Tag1 { } public class Tag2 { } public class Tag3 { } [SerializeField] [Label(typeof(Tag1))] private string _label1; [SerializeField] [Label(typeof(Tag2))] private string _label2; [SerializeField] [Label(typeof(Tag3))] private string _label3; TextFieldにLabelAttributeを指定し 引数に先ほどのTagをそれぞれ指定 } © DeNA Co., Ltd. 20
属性を別の型に依存させてみる 1 [EnumLabel(typeof(LabelSample.Tag1))] [EnumLabel(typeof(LabelSample.Tag2))] [EnumLabel(typeof(LabelSample.Tag3))] public enum SampleType { Hoge, Fuga, Piyo } © DeNA Co., Ltd. 21
属性を別の型に依存させてみる 1 [EnumLabel(typeof(LabelSample.Tag1))] [EnumLabel(typeof(LabelSample.Tag2))] [EnumLabel(typeof(LabelSample.Tag3))] public enum SampleType { Hoge, Fuga, EnumLabelAttributeの引数に LabelSampleのTagクラスを指定 EnumLabelAttributeでAllowMultiple=trueを 指定しているため複数指定可能 Piyo } © DeNA Co., Ltd. 22
属性を別の型に依存させてみる 1 [EnumLabel(typeof(LabelSample.Tag1))] [EnumLabel(typeof(LabelSample.Tag2))] [EnumLabel(typeof(LabelSample.Tag3))] public enum SampleType { Hoge, Fuga, EnumLabelAttributeの引数に LabelSamplenoTagクラスを指定 EnumLabelAttributeでAllowMultiple=trueを 指定しているため複数指定可能 Piyo } 各テキストフィールドとEnumを 間接的に紐づけることができました! © DeNA Co., Ltd. 23
属性を別の型に依存させてみる 1 public class LabelAttribute : PropertyAttribute public class LabelSample : MonoBehaviour [EnumLabel(typeof(LabelSample.Tag1))] { { [EnumLabel(typeof(LabelSample.Tag2))] public Type Tag { get; } public class Tag1 { public LabelAttribute(Type tag) } { [EnumLabel(typeof(LabelSample.Tag3))] public enum SampleType { Tag = tag; Hoge, public class Tag2 } } { Fuga, } Piyo [AttributeUsage(AttributeTargets.Enum, AllowMultiple = true)] } public class EnumLabelAttribute : Attribute public class Tag3 { { public Type Tag { get; } } public EnumLabelAttribute(Type tag) [SerializeField] [Label(typeof(Tag1))] private string _label1; { Tag = tag; [SerializeField] [Label(typeof(Tag2))] private string _label2; } } © DeNA Co., Ltd. [SerializeField] [Label(typeof(Tag3))] private string _label3; } 24
あとはPropertyDrawerを実装するだけ 1 public interface ILabelProvider { string[] Labels { get; } } public class EnumLabelProvider : ILabelProvider { public string[] Labels { get; } public EnumLabelProvider(Type enumType) { Labels = Enum.GetNames(enumType); } } © DeNA Co., Ltd. 25
あとはPropertyDrawerを実装するだけ 1 public interface ILabelProvider { string[] Labels { get; } } PropertyDrawerでstringに変換する インターフェースを定義 public class EnumLabelProvider : ILabelProvider { public string[] Labels { get; } public EnumLabelProvider(Type enumType) { Labels = Enum.GetNames(enumType); } } © DeNA Co., Ltd. 26
あとはPropertyDrawerを実装するだけ 1 public interface ILabelProvider { string[] Labels { get; } } public class EnumLabelProvider : ILabelProvider { public string[] Labels { get; } ここではEnumをStringに変換する EnumLabelProviderを実装 public EnumLabelProvider(Type enumType) { Labels = Enum.GetNames(enumType); } } © DeNA Co., Ltd. 27
あとはPropertyDrawerを実装するだけ
1
[CustomPropertyDrawer(typeof(LabelAttribute))]
internal class LabelPropertyDrawer : PropertyDrawer
{
private static Dictionary<Type, List<ILabelProvider>> s_providersGroups;
private string[] _options;
private int _selectedIndex;
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
if (s_providersGroups == null)
{
CreateProviders();
}
if (_options == null)
{
CreateOptions(property);
}
Draw(position, property, label);
}
}
© DeNA Co., Ltd.
28
あとはPropertyDrawerを実装するだけ
1
[CustomPropertyDrawer(typeof(LabelAttribute))]
internal class LabelPropertyDrawer : PropertyDrawer
{
private static Dictionary<Type, List<ILabelProvider>> s_providersGroups;
private string[] _options;
staticなILabelProviderのDictionaryを用意
複数指定想定のためListを採用
private int _selectedIndex;
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
if (s_providersGroups == null)
{
CreateProviders();
}
if (_options == null)
{
CreateOptions(property);
}
Draw(position, property, label);
}
}
© DeNA Co., Ltd.
29
あとはPropertyDrawerを実装するだけ
1
[CustomPropertyDrawer(typeof(LabelAttribute))]
internal class LabelPropertyDrawer : PropertyDrawer
EditorGUI.Popupに渡すoptionsを生成
{
private static Dictionary<Type, List<ILabelProvider>> s_providersGroups;
private string[] _options;
private int _selectedIndex;
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
EditorGUI.Popupではoptionsとして
stringの配列を渡すとプルダウンになる
{
if (s_providersGroups == null)
{
CreateProviders();
}
if (_options == null)
{
CreateOptions(property);
}
Draw(position, property, label);
}
}
© DeNA Co., Ltd.
30
あとはPropertyDrawerを実装するだけ
1
[CustomPropertyDrawer(typeof(LabelAttribute))]
internal class LabelPropertyDrawer : PropertyDrawer
{
private static Dictionary<Type, List<ILabelProvider>> s_providersGroups;
private string[] _options;
private int _selectedIndex;
現在選択中のIndex
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
if (s_providersGroups == null)
{
CreateProviders();
}
if (_options == null)
{
CreateOptions(property);
}
Draw(position, property, label);
}
}
© DeNA Co., Ltd.
31
あとはPropertyDrawerを実装するだけ
1
[CustomPropertyDrawer(typeof(LabelAttribute))]
internal class LabelPropertyDrawer : PropertyDrawer
{
private static Dictionary<Type, List<ILabelProvider>> s_providersGroups;
private string[] _options;
private int _selectedIndex;
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
if (s_providersGroups == null)
{
CreateProviders();
初回のみProviderを生成
}
if (_options == null)
{
CreateOptions(property);
}
Draw(position, property, label);
}
}
© DeNA Co., Ltd.
32
あとはPropertyDrawerを実装するだけ
1
private static void CreateProviders()
{
s_providersGroups = new Dictionary<Type, List<ILabelProvider>>();
var enumTypes = TypeCache.GetTypesWithAttribute<EnumLabelAttribute>();
foreach (var enumType in enumTypes)
{
var provider = new EnumLabelProvider(enumType);
foreach (var attr in enumType.GetCustomAttributes<EnumLabelAttribute>())
{
if (!s_providersGroups.TryGetValue(attr.Tag, out var providers))
{
s_providersGroups[attr.Tag] = providers = new List<ILabelProvider>();
}
providers.Add(provider);
}
}
}
© DeNA Co., Ltd.
33
あとはPropertyDrawerを実装するだけ
1
private static void CreateProviders()
{
s_providersGroups = new Dictionary<Type, List<ILabelProvider>>();
TypeCacheを使ってEnumLabelAttributeの
var enumTypes = TypeCache.GetTypesWithAttribute<EnumLabelAttribute>();
ついたEnumTypeを取得
foreach (var enumType in enumTypes)
{
var provider = new EnumLabelProvider(enumType);
foreach (var attr in enumType.GetCustomAttributes<EnumLabelAttribute>())
{
TypeCacheはリフレクションを使うより
高速かつ簡潔に書けるのでおすすめ
if (!s_providersGroups.TryGetValue(attr.Tag, out var providers))
{
s_providersGroups[attr.Tag] = providers = new List<ILabelProvider>();
}
providers.Add(provider);
}
}
}
© DeNA Co., Ltd.
34
あとはPropertyDrawerを実装するだけ
1
private static void CreateProviders()
{
s_providersGroups = new Dictionary<Type, List<ILabelProvider>>();
var enumTypes = TypeCache.GetTypesWithAttribute<EnumLabelAttribute>();
foreach (var enumType in enumTypes)
{
var provider = new EnumLabelProvider(enumType);
foreach (var attr in enumType.GetCustomAttributes<EnumLabelAttribute>())
{
if (!s_providersGroups.TryGetValue(attr.Tag, out var providers))
{
s_providersGroups[attr.Tag] = providers = new List<ILabelProvider>();
}
集めたEnumの中から
EnumLabelAttributeを探し
ProviderにEnumLabelProviderを格納
providers.Add(provider);
}
}
}
© DeNA Co., Ltd.
35
あとはPropertyDrawerを実装するだけ
1
[CustomPropertyDrawer(typeof(LabelAttribute))]
internal class LabelPropertyDrawer : PropertyDrawer
{
private static Dictionary<Type, List<ILabelProvider>> s_providersGroups;
private string[] _options;
private int _selectedIndex;
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
if (s_providersGroups == null)
{
CreateProviders();
}
if (_options == null)
{
CreateOptions(property);
Optionsの生成処理
}
Draw(position, property, label);
}
}
© DeNA Co., Ltd.
36
あとはPropertyDrawerを実装するだけ
1
private void CreateOptions(SerializedProperty property)
{
_options = Array.Empty<string>();
if (attribute is LabelAttribute attr)
{
if (s_providersGroups!.TryGetValue(attr.Tag, out var providers))
{
using var _ = ListPool<string>.Get(out var options);
options.Add("手動入力");
foreach (var provider in providers)
{
options.AddRange(provider.Labels);
}
_options = options.ToArray();
// 初期値がoptionsの中に見つからない場合は自由入力欄用のインデックスを指定
var initialValue = property.stringValue;
var index = Array.FindIndex(_options, 1, option => option == initialValue);
_selectedIndex = index >= 0 ? index : 0;
}
}
}
© DeNA Co., Ltd.
37
あとはPropertyDrawerを実装するだけ
1
private void CreateOptions(SerializedProperty property)
{
_options = Array.Empty<string>();
if (attribute is LabelAttribute attr)
{
if (s_providersGroups!.TryGetValue(attr.Tag, out var providers))
{
attributeをLabelAttributeにcastして
using var _ = ListPool<string>.Get(out var options);
options.Add("手動入力");
foreach (var provider in providers)
{
options.AddRange(provider.Labels);
対応するProviderが存在したら
Optionsに格納するだけ!
}
_options = options.ToArray();
手動入力をサポートするための処理もあります
// 初期値がoptionsの中に見つからない場合は自由入力欄用のインデックスを指定
var initialValue = property.stringValue;
var index = Array.FindIndex(_options, 1, option => option == initialValue);
_selectedIndex = index >= 0 ? index : 0;
}
}
}
© DeNA Co., Ltd.
38
あとはPropertyDrawerを実装するだけ
1
[CustomPropertyDrawer(typeof(LabelAttribute))]
internal class LabelPropertyDrawer : PropertyDrawer
{
private static Dictionary<Type, List<ILabelProvider>> s_providersGroups;
private string[] _options;
private int _selectedIndex;
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
if (s_providersGroups == null)
{
CreateProviders();
}
if (_options == null)
{
CreateOptions(property);
}
Draw(position, property, label);
プロパティの描画処理
}
}
© DeNA Co., Ltd.
39
まだ終わりません © DeNA Co., Ltd. 40
カスタムラベルもサポートする
1
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
public class CustomLabelAttribute : Attribute
{
public Type Tag { get; }
public CustomLabelAttribute(Type tag)
{
Tag = tag;
}
}
[CustomLabel(typeof(LabelSample.Tag1))]
[CustomLabel(typeof(LabelSample.Tag2))]
[CustomLabel(typeof(LabelSample.Tag3))]
public class SampleLabelProvider : ILabelProvider
{
public string[] Labels { get; } = { "Custom1", "Custom2", "Custom3" };
}
© DeNA Co., Ltd.
プロパティの描画処理
41
カスタムラベルもサポートする
1
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
public class CustomLabelAttribute : Attribute
{
public Type Tag { get; }
ILabelProviderを実装したクラス用の
public CustomLabelAttribute(Type tag)
Attributeを定義
{
Tag = tag;
}
}
[CustomLabel(typeof(LabelSample.Tag1))]
[CustomLabel(typeof(LabelSample.Tag2))]
[CustomLabel(typeof(LabelSample.Tag3))]
public class SampleLabelProvider : ILabelProvider
{
public string[] Labels { get; } = { "Custom1", "Custom2", "Custom3" };
}
© DeNA Co., Ltd.
プロパティの描画処理
42
カスタムラベルもサポートする
1
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
public class CustomLabelAttribute : Attribute
{
public Type Tag { get; }
public CustomLabelAttribute(Type tag)
{
Tag = tag;
}
}
[CustomLabel(typeof(LabelSample.Tag1))]
[CustomLabel(typeof(LabelSample.Tag2))]
[CustomLabel(typeof(LabelSample.Tag3))]
public class SampleLabelProvider : ILabelProvider
ILabelProviderを実装して
Attributeを追加
{
public string[] Labels { get; } = { "Custom1", "Custom2", "Custom3" };
}
© DeNA Co., Ltd.
プロパティの描画処理
43
カスタムラベルもサポートする
1
private static void AppendCustomLabelProviders()
{
var providerTypes = TypeCache.GetTypesDerivedFrom<ILabelProvider>();
foreach (var providerType in providerTypes)
{
if (providerType.IsAbstract || providerType.IsInterface)
{
continue;
}
var attrs = providerType.GetCustomAttributes<CustomLabelAttribute>().ToArray();
if (attrs.Length == 0)
{
if (!providerType.IsAssignableFrom(typeof(EnumLabelProvider)))
あとはEnumProviderと同じように
{
Debug.LogError($"{providerType.Name}に{typeof(CustomLabelAttribute)}を指定してください。 ");
}
Providerの生成処理をゴリゴリ実装するだけ!
continue;
}
foreach (var attr in attrs)
{
if (!s_providersGroups.TryGetValue(attr.Tag, out var providers))
{
s_providersGroups[attr.Tag] = providers = new List<ILabelProvider>();
}
providers.Add((ILabelProvider)Activator.CreateInstance(providerType));
}
}
}
© DeNA Co., Ltd.
44
最終的なタイトル側とライブラリ側の実装
1
ライブラリ側
タイトル側
[EnumLabel(typeof(LabelSample.Tag1))]
public class LabelSample : MonoBehaviour
[EnumLabel(typeof(LabelSample.Tag3))]
{
public enum SampleType
public class Tag1
{
{
Hoge,
}
Fuga,
Piyo
public class Tag2
}
{
}
[CustomLabel(typeof(LabelSample.Tag2))]
[CustomLabel(typeof(LabelSample.Tag3))]
public class Tag3
public class SampleLabelProvider : ILabelProvider
{
{
}
public string[] Labels { get; } = { "Custom1", "Custom2", "Custom3" };
}
[SerializeField] [Label(typeof(Tag1))] private string _label1;
[SerializeField] [Label(typeof(Tag2))] private string _label2;
[SerializeField] [Label(typeof(Tag3))] private string _label3;
}
© DeNA Co., Ltd.
45
1 最終的なゴール ● 属性をつけるだけで拡張可能 ● Enumの値をプルダウンにしている ● stringでの自由入力も可能 ● stringでSerializeされる ※発表は動画でした 動画はイベントアーカイブをご覧ください © DeNA Co., Ltd. 46
まとめ 1 ● TextField用の属性とラベル用の属性を定義して別の型に依存させることで 間接的に紐づけることができた ● PropertyDrawerを使ってTextFieldを任意のラベルによるプルダウン化 ● TextFieldをプルダウンにすることで入力ミスを削減 ● 自由入力もサポートしてstringとenumの両方の性質を持つTextFieldが生まれた © DeNA Co., Ltd. 47
© DeNA Co., Ltd. 48