はなちるのマイノート

Unityをメインとした技術ブログ。自分らしくまったりやっていきたいと思いますー!

【C#, Unity】自作Attributeの作り方とその応用例(PropertyAttribute)

作り方

System.Attributeを直接的・間接的に継承した派生クラスを作成することで自作Attributeを作成できます。

またクラス名は〇〇Attributeのように付けることが推奨されています。

public class AuthorAttribute : System.Attribute  
{  

} 

コンストラクターとパブリックな読み取り/書き込みフィールドまたはプロパティ

public class AuthorAttribute : System.Attribute  
{  
    private string name;  
    public double version;  
  
    public AuthorAttribute(string name)  
    {  
        this.name = name;  
        version = 1.0;  
    }  
} 

コンストラクターのパラメーター・パブリックな読み取り/書き込みフィールドまたはプロパティについて、公式ドキュメントでは以下のように表記されています。

コンストラクターのパラメーターはカスタム属性の位置指定パラメーターです。 この例では、name が位置指定パラメーターになります。 パブリックな読み取り/書き込みフィールドまたはプロパティは名前付きパラメーターです。 この場合は、version が唯一の名前付きパラメーターです。

少し分かりにくいですが、コンストラクターのパラメーターは以下のように指定できます。

[Author("Name")]

対してpublicなフィールド・プロパティは以下のように指定します。

[Author("Name", version = 2.0)]

System.AttributeUsageの利用

System.AttributeUsage属性を自作Attributeに対して使用することで、以下のような制限をつけることができます。
C# コンパイラによって解釈される属性: その他 | Microsoft Docs

できること
[System.AttributeUsage(System.AttributeTargets.Class |  System.AttributeTargets.Struct)  ]  
public class AuthorAttribute : System.Attribute  
{  
    private string name;  
    public double version;  
  
    public AuthorAttribute(string name)  
    {  
        this.name = name;  
        version = 1.0;  
    }  
} 


また規定値は以下のようですね。

[AttributeUsage(AttributeTargets.All, AllowMultiple = false, Inherited = true)]
プロパティ名 意味
ValidOn この位置指定パラメーターは、指定された属性を配置できるプログラム要素を指定します。
AllowMultiple 指定された属性を特定のプログラム要素に対して複数指定できるかどうかを指定します。
Inherited 指定された属性を派生クラスとオーバーライドするメンバーによって継承できるかどうかを指定します。

AttributeUsageAttribute クラス (System) | Microsoft Docs

リフレクションで属性の情報を取得

属性の情報を読み取る手段としてリフレクションを利用します。
docs.microsoft.com

public class CustomAttribute : Attribute
{
    public readonly int value;

    public CustomAttribute(int value)
    {
        this.value = value;
    }
}

[CustomAttribute(2)]
public class SampleClass
{
    [CustomAttribute(5)]
    public string sampleField;
    
    [CustomAttribute(9)]
    public string SampleProperty { get; set; }

    [CustomAttribute(4)]
    public void SampleMethod()
    {
        
    }
}
// クラスに付与されたCustomAttributeの情報を取り出す
var customAttribute = typeof(SampleClass).GetCustomAttribute<CustomAttribute>();
Console.WriteLine(customAttribute.value);       // 2

// メソッドに付与されたCustomAttributeの情報を取り出す
customAttribute = typeof(SampleClass).GetMethod("SampleMethod").GetCustomAttribute<CustomAttribute>();
Console.WriteLine(customAttribute.value);       // 4
        
// フィールドに付与されたCustomAttributeの情報を取り出す
customAttribute = typeof(SampleClass).GetField("sampleField").GetCustomAttribute<CustomAttribute>();
Console.WriteLine(customAttribute.value);       // 5
        
// プロパティに付与されたCustomAttributeの情報を取り出す
customAttribute = typeof(SampleClass).GetProperty("SampleProperty").GetCustomAttribute<CustomAttribute>();
Console.WriteLine(customAttribute.value);       // 9

UnityのPropertyAttribute

UnityにはPropertyAttributeというSystem.Attributeを継承したクラスが用意されており、PropertyDrawerと組み合わせることでインスペクターでの表示を変更することができます。

カスタムプロパティー属性を派生させるベースクラス。これを使用してスクリプト変数のカスタム属性を作成します。

カスタム属性は PropertyDrawer クラスと連結して、その属性があるスクリプト変数がインスペクター上でどう表示されるか制御します。

UnityEngine.PropertyAttribute - Unity スクリプトリファレンス

Unity内部の実装はシンプルで、以下のように実装されています。

// Base class to derive custom property attributes from. Use this to create custom attributes for script variables.
[System.AttributeUsage(AttributeTargets.Field, Inherited = true, AllowMultiple = false)]
[UnityEngine.Scripting.UsedByNativeCode]
public abstract class PropertyAttribute : Attribute
{
    public int order { get; set; }
}

UnityCsReference/PropertyAttribute.cs at master · Unity-Technologies/UnityCsReference · GitHub

Unityで実装されているPropertyAttribute

UnityではPropertyAttributeを継承している属性が複数あります。
UnityCsReference/PropertyAttribute.cs at master · Unity-Technologies/UnityCsReference · GitHub

// Attribute used to make a float or int variable in a script be restricted to a specific range.
[System.AttributeUsage(AttributeTargets.Field, Inherited = true, AllowMultiple = false)]
public sealed class RangeAttribute : PropertyAttribute
{
    public readonly float min;
    public readonly float max;

    // Attribute used to make a float or int variable in a script be restricted to a specific range.
    public RangeAttribute(float min, float max)
    {
        this.min = min;
        this.max = max;
    }
}

基本全部public readonlyなフィールド&コンストラクタで設定というパターンなようです。

自作PropertyAttribute

public class DisableAttribute : PropertyAttribute { }

PropertyDrawerを継承したクラスを利用することで、PropertyAttributeにアクセスし、インスペクターの表示を変更することができます。
UnityEditor.PropertyDrawer - Unity スクリプトリファレンス

[CustomPropertyDrawer(typeof(DisableAttribute))]
public class DisableDrawer : PropertyDrawer
{
    public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
    {
        EditorGUI.BeginDisabledGroup(true);
        EditorGUI.PropertyField(position, property, label);
        EditorGUI.EndDisabledGroup();
    }
}


実際に利用してみると以下のようになります。

public class Test : MonoBehaviour
{
    [Disable]
    public string sample;
}
Disable属性をつけた様子

またAttribute内の変数を利用したい場合は、PropertyDrawer.attributeを利用します。

DisableAttribute range = (DisableAttribute)attribute;