0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【VRChat】Attributeを用いたUdonSharpのインスペクター拡張

0
Posted at

概要

Unity のスクリプトで,インスペクターの表示をいじりたいけどカスタムインスペクタを作るほどではない,というときに,カスタムの Attribute を使うと便利です.

UdonSharp でも同じようにカスタムの Attribute が使えたので紹介です.

スクリーンショット 2026-02-15 103635.png

Attribute について

UdonSharp を書いたことのある方であれば,変数の前に [SerializedField][UdonSynced] といったおまじないが書いてあるのを見たことがあると思います.
あれが Attribute です.筆者もあまり理解していませんが,Attribute を付けることで,その変数のふるまいを操作することができ,インスペクター上の表示もいじれます.

そして,Attribute は自作でき,ある程度であれば UdonSharp でも使えるようです.

Attribute を作る

さっそく作りましょう.Enum みたいなポップアップを作ってみます.

Attribute 自体は Udon ではないので,普通の C# スクリプトとして作ります.
スクリプトを作りたい場所で「右クリック > Create > C# Script」で作れます.
名前は EnumPopupFieldAttribute としておきます.(実は名前は何でもいいらしいです.)

スクリーンショット 2026-02-15 104619.png

スクリプトを作る場所ですが,Editor とか EditorOnly みたいな名前のフォルダの下に作ると,使うときエラーになるので注意してください.

スクリプトの中身はこんな感じにします.int 型の変数につける想定です.

EnumPopupFieldAttribute.cs
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」でアセンブリとスクリプトが両方できます.

スクリーンショット 2026-02-15 100715.png

適当に publicint な変数につけてみましょう.
さっき作ったクラスは EnumPopupFieldAttribute でしたが,使うときは EnumPopupField です.不思議ですね.

EnumPopupTest.cs
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 なので,選んだ選択肢は整数として保存されます.

スクリーンショット 2026-02-15 101229.png

いい感じですね.

雑記

UdonSharp でカスタムの Attribute が使えることを知りませんでした.結構便利ですね.
ReadOnly のフィールドとかも簡単に作れます.

ReadOnlyFieldAttribute.cs
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 が必要です.

EnumPopupTest.cs
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

}

これで冒頭に掲載した画が完成します.

スクリーンショット 2026-02-15 103635.png

いい感じですね.

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?