概要
Unity のスクリプトで,インスペクターの表示をいじりたいけどカスタムインスペクタを作るほどではない,というときに,カスタムの Attribute を使うと便利です.
UdonSharp でも同じようにカスタムの Attribute が使えたので紹介です.
Attribute について
UdonSharp を書いたことのある方であれば,変数の前に [SerializedField] や [UdonSynced] といったおまじないが書いてあるのを見たことがあると思います.
あれが Attribute です.筆者もあまり理解していませんが,Attribute を付けることで,その変数のふるまいを操作することができ,インスペクター上の表示もいじれます.
そして,Attribute は自作でき,ある程度であれば UdonSharp でも使えるようです.
Attribute を作る
さっそく作りましょう.Enum みたいなポップアップを作ってみます.
Attribute 自体は Udon ではないので,普通の C# スクリプトとして作ります.
スクリプトを作りたい場所で「右クリック > Create > C# Script」で作れます.
名前は EnumPopupFieldAttribute としておきます.(実は名前は何でもいいらしいです.)
スクリプトを作る場所ですが,Editor とか EditorOnly みたいな名前のフォルダの下に作ると,使うときエラーになるので注意してください.
スクリプトの中身はこんな感じにします.int 型の変数につける想定です.
using System;
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
#endif
[AttributeUsage(AttributeTargets.Field, Inherited = true, AllowMultiple = false)]
public class EnumPopupFieldAttribute : PropertyAttribute
{
public GUIContent[] enumKeys { get; }
public EnumPopupFieldAttribute(params string[] enumKeys)
{
if (enumKeys == null || enumKeys.Length == 0)
{
enumKeys = new string[] { "Default" };
}
GUIContent[] contents = new GUIContent[enumKeys.Length];
for (int i = 0; i < enumKeys.Length; i++)
{
contents[i] = new GUIContent(enumKeys[i]);
}
this.enumKeys = contents;
}
}
#if UNITY_EDITOR
[CustomPropertyDrawer(typeof(EnumPopupFieldAttribute))]
public class EnumPopupFieldAttributeDrawer : PropertyDrawer
{
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
if (property.propertyType != SerializedPropertyType.Integer)
{
// int以外のフィールドにアトリビュートが付けられていた場合のエラー表示
position = EditorGUI.PrefixLabel(position, label);
int old = EditorGUI.indentLevel;
EditorGUI.indentLevel = 0;
EditorGUI.LabelField(position, "Use EnumPopupFieldAttribute with int.");
EditorGUI.indentLevel = old;
return;
}
EnumPopupFieldAttribute attr = (EnumPopupFieldAttribute)attribute;
int oldValueRaw = property.intValue;
int oldValueClamped = Mathf.Clamp(oldValueRaw, 0, attr.enumKeys.Length - 1);
int newValueRaw = EditorGUI.Popup(position, label, oldValueClamped, attr.enumKeys);
int newValueClamped = Mathf.Clamp(newValueRaw, 0, attr.enumKeys.Length - 1);
if (newValueClamped != oldValueRaw)
{
property.intValue = newValueClamped;
property.serializedObject.ApplyModifiedProperties();
}
}
}
#endif
おまじないがいっぱい書いてありますが,Udon を書く分にはあまりかかわらない内容なので,あまり気にしないでください.筆者も正直よくわかっていません.
Attribute を使う
作った Attribute を使ってみましょう.
適当な UdonSharp を作ります.プログラムアセットを作りたい場所で「右クリック > Create > U# Script」でアセンブリとスクリプトが両方できます.
適当に public で int な変数につけてみましょう.
さっき作ったクラスは EnumPopupFieldAttribute でしたが,使うときは EnumPopupField です.不思議ですね.
using UdonSharp;
using UnityEngine;
public class EnumPopupTest : UdonSharpBehaviour
{
[EnumPopupField("Option A", "Option B", "Option C")]
public int selectedOption;
#if UNITY_EDITOR
void OnValidate()
{
Debug.Log("Selected Option: " + selectedOption);
}
#endif
}
EnumPopupField の引数に列挙した文字列がインスペクター上でポップアップとして表示されます.変数自体は int なので,選んだ選択肢は整数として保存されます.
いい感じですね.
雑記
UdonSharp でカスタムの Attribute が使えることを知りませんでした.結構便利ですね.
ReadOnly のフィールドとかも簡単に作れます.
using System;
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
#endif
[AttributeUsage(AttributeTargets.Field, Inherited = true, AllowMultiple = false)]
public class ReadOnlyFieldAttribute : PropertyAttribute {}
#if UNITY_EDITOR
[CustomPropertyDrawer(typeof(ReadOnlyFieldAttribute))]
public class ReadOnlyFieldAttributeDrawer : PropertyDrawer
{
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
using (new EditorGUI.DisabledGroupScope(true))
{
EditorGUI.PropertyField(position, property, label);
}
}
}
#endif
さっきのスクリプトに追加してみましょう.
Reflection を使っているので !COMPILER_UDONSHARP が必要です.
using UdonSharp;
using UnityEngine;
#if UNITY_EDITOR && !COMPILER_UDONSHARP
using System.Reflection;
#endif
public class EnumPopupTest : UdonSharpBehaviour
{
[EnumPopupField("Option A", "Option B", "Option C")]
public int selectedOption;
[ReadOnlyField] public string selectedOptionName;
#if UNITY_EDITOR && !COMPILER_UDONSHARP
void OnValidate()
{
Debug.Log("Selected Option: " + selectedOption);
var flg = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;
var field = typeof(EnumPopupTest).GetField(nameof(selectedOption), flg);
var attr = (EnumPopupFieldAttribute)field.GetCustomAttribute(typeof(EnumPopupFieldAttribute));
if (attr != null)
{
if (selectedOption >= 0 && selectedOption < attr.enumKeys.Length)
{
selectedOptionName = attr.enumKeys[selectedOption].text;
}
else
{
selectedOptionName = "Invalid Option";
}
}
}
#endif
}
これで冒頭に掲載した画が完成します.
いい感じですね.



