(:3[kanのメモ帳]

個人ゲーム開発者kan.kikuchiのメモ的技術ブログ。月木更新でUnity関連がメイン。

属性(Attribute)とは【C#】【Unity】【属性】


このエントリーをはてなブックマークに追加

この記事でのバージョン
Unity 5.6.1f1


はじめに

今回はタイトル通り、属性(Attribute)とはなんぞや?という感じの記事です。

なお、基本的にUnity+C#での話になりますので、あしからず。


属性とは

では早速、属性とは?という話ですが、

MicrosoftやUnityのドキュメントには以下のように説明されています。

属性は、アセンブリ、型、メソッド、プロパティなどの宣言に関係する情報 (メタデータ) をコードに関連付ける効果的な方法の 1 つです。

属性 (C# および Visual Basic)

Attribute (属性) はスクリプトのクラス、プロパティー、関数の前に記載する特別な動作指示です。

Unity - マニュアル: 属性


どちらも言わんとしている事は同じで、

属性とはプロパティや関数などの宣言時に情報を付加するモノと言った感じです。


使い方

属性の使い方はいたって簡単、宣言部分の上に[]を付けて属性を記述するだけです。

なお、同時に複数の変数を宣言した場合、両方に属性が付加されることになります。

//NewClassにSerializableという属性を付加
[Serializable]
public class NewClass {}
//_num1と_num2にSerializeFieldという属性を付加
[SerializeField]
private int _num1 = 1, _num2 = 2;


また、複数の属性を同時に付加することも可能ですし、

なんらかのパラメータを渡せる属性もあります。

//NewClassにSerializableという属性と、Headerという属性にパラメータ(------Test------)を設定して付加
[SerializeField][Header("------Test------")]
private int _test = 0;


せっかくなので具体的な属性についても見ていきましょう。


まず、個人的に一番使う属性SerializeField

これはprivateなフィールド(メンバ変数)を強制的にシリアライズすることで、

Inspectorでの表示を可能にします。

[SerializeField]
private int _num = 1;

f:id:kan_kikuchi:20170622071124p:plain


次にSerializableですが、

こちらはクラスをシリアライズするためのものです。

そうする事によりテキストファイル等に書き出せるようになりますし、

UnityではInspectorでの表示を可能にします。

[Serializable]
public class NewClass {
  public int Num = 1;
}

public NewClass newClass = new NewClass();

f:id:kan_kikuchi:20170622072947p:plain


なお、SerializeFieldはUnityの属性で、

SerializableはC#(.NET)の属性です。


オリジナル属性の作り方

前述の通り、UnityやC#にはいくつかの属性があらかじめ用意されていますが、

オリジナルの属性を作ることも可能です。


例えばTestという属性を作ろうとした場合

以下のようにPropertyAttributeを継承したTestAttributeを作ります。

(Attributeを継承してもできるが今回は割愛)

using UnityEngine;

public class TestAttribute : PropertyAttribute {
	
}


なお、基本的にクラス名の語尾にはAttributeを付けます。

そうする事で、属性を使う際にAttributeを省略する事が出来るようになります。

//そのままでも使えるし
[TestAttribute]
public float Test1 = 0;

//省略しても同じように使える
[Test]
public float Test2 = 0;


これで既にTestという属性は使えるようになりましたが、

付加してもなにも起きない状態です。


Inspector上の見た目を変更

Inspector上の見た目を変更を変更するには、PropertyDrawer

継承したTestDrawerをEditorディレクトリ内に作成します。

using UnityEngine;
using UnityEditor;

//CustomPropertyDrawerでTestAttributeを指定(TestAttributeのInspector上の見た目を変更するため)
[CustomPropertyDrawer(typeof(TestAttribute))]
public class TestDrawer : PropertyDrawer{

  //Inspector上の見た目を設定
  public override void OnGUI (Rect position, SerializedProperty property, GUIContent label){
    
    //Testと書かれたラベルを設定
    EditorGUI.LabelField(position, "Test");

  }
}

f:id:kan_kikuchi:20170626070631p:plain


宣言時にパラメータを渡し、PropertyDrawer側でその値を使う場合は

以下のような感じになります。

using UnityEngine;

public class TestAttribute : PropertyAttribute {

  //宣言時に渡された文字列
  public string Text;

  /// <summary>
  /// コンストラクタ(引数で文字列を渡す)
  /// </summary>
  public TestAttribute (string text){
    Text = text;
  }

}
using UnityEngine;
using UnityEditor;

//CustomPropertyDrawerでTestAttributeを指定(TestAttributeのInspector上の見た目を変更するため)
[CustomPropertyDrawer(typeof(TestAttribute))]
public class TestDrawer : PropertyDrawer{

  //Inspector上の見た目を設定
  public override void OnGUI (Rect position, SerializedProperty property, GUIContent label){
    //TestAttributeを取得
    TestAttribute testAttribute = (TestAttribute)attribute;

    //TestAttributeのTextに設定されている文字列をラベルに設定
    EditorGUI.LabelField(position, testAttribute.Text);

  }
}
[Test("テキスト!")]
public float Test = 0;

f:id:kan_kikuchi:20170627065017p:plain


上記の例ではただラベルを表示しているだけですが、ここでなんらか処理を挟む事ももちろん可能です。


属性を付加する対象を設定

属性を付加する対象を設定するには、AttributeUsageを使います。

using System;
using UnityEngine;

//Enumにだけ設定出来るように
[AttributeUsage( AttributeTargets.Enum)]
public class TestAttribute : PropertyAttribute {
}


設定した対象以外に属性を付けようとすると、以下のようにエラーが発生するようになります。

error CS0592: The attribute `TestAttribute' is not valid on this declaration type. It is valid on `enum' declarations only

f:id:kan_kikuchi:20170627065917p:plain


付加した属性の取得

付加した属性の取得の行う場合は、Attribute.GetCustomAttribute(s)を使います。

using UnityEngine;
using System;

public class NewBehaviourScript1 : MonoBehaviour {

  //属性を取得する対象
  public NewBehaviourScript Script;

  private void Start () {

    TestAttribute testAttribute = Attribute.GetCustomAttribute(
      Script.GetType().GetField("Test"),//ScriptにあるTestというフィールド変数についている、
      typeof(TestAttribute)             //TestAttributeという属性を取得
    ) as TestAttribute;

    //取得した属性に設定されているテキストをログで表示
    Debug.Log(testAttribute.Text);

  }

}

f:id:kan_kikuchi:20170627071958p:plain


おわりに

オリジナルの属性は作らなくても困る事は少ないですが、色々と便利になる場面はあります。


例えば、ドラック&ドロップでパスを取得したり、


f:id:kan_kikuchi:20170627072517g:plain


ビットフラグを使いやすくしたり、


f:id:kan_kikuchi:20170627072507j:plain


ちょっとした事でかなり楽になったりするので、適宜使っていきたいですね(:3っ)∋〜


参考