【Unity】Unity Attribute活用ガイド:コードに特別な動作を与えるメタデータマーカー

作成: 2026-02-05最終更新: 2026-07-13

C#のAttributeを使ってUnityのインスペクターをカスタマイズする方法を解説。SerializeField、Header、Range、RequireComponentなど実践的な使い方を紹介します。

動作確認環境: Unity 2022.3 LTS / Unity 6

「インスペクターにprivate変数を表示したいけど、どうすればいいんだろう…」「フィールドが増えてきて、インスペクターがのっぺりした長いリストになってしまった…」

そこで役立つのが、Attribute(属性) です。Attributeは、C#のメタデータマーカー——つまり「コードに貼る指示ラベル」で、変数やクラスの宣言の上に[...]の形で置くだけで、Unityに特別な動作を指示できます。

[HideInInspector]
public float strength;
Attributeのイメージ。コードブロックに指示ラベルのタグを貼り付けている

この記事でわかること

  • インスペクターを見やすく安全にする定番Attribute(SerializeFieldHeaderRangeなど)
  • HideInInspectorNonSerializedの正確な違い
  • クラスに付けるRequireComponentCreateAssetMenuなどの働き
  • カスタムAttribute+PropertyDrawerの作り方(Editorフォルダの決まりごと)

Sponsored

まず覚える7個:武器データを壊れにくくする

Attributeは非常に数が多いのですが、 毎日使うのは7個程度 です。まずはこれだけ覚えれば、実用的なインスペクターが作れます。題材は「武器のパラメータ」——攻撃力・連射間隔・弾数・説明文を持つWeaponConfigを、誤入力に強い形に仕上げます。

// ファイル名: WeaponConfig.cs — 武器1種のパラメータ
using UnityEngine;

public class WeaponConfig : MonoBehaviour
{
    [Header("攻撃設定")]
    [Tooltip("1発あたりのダメージ")]
    [Range(1, 100)]
    [SerializeField] private int attackPower = 20;

    [Tooltip("連射間隔(秒)。小さいほど速い")]
    [Min(0.05f)]
    [SerializeField] private float fireInterval = 0.5f;

    [Tooltip("装弾数")]
    [Min(0)]
    [SerializeField] private int magazineSize = 12;

    [Header("フレーバー")]
    [TextArea(3, 6)]
    [SerializeField] private string description;

    [ContextMenu("発射テスト")]
    private void TestFire()
    {
        Debug.Log($"{name}: 攻撃力{attackPower} / {fireInterval}秒間隔 / 弾数{magazineSize}");
    }
}

たった30行ですが、7個のAttributeがそれぞれ仕事をしています。

Attribute役割この武器での仕事
[SerializeField]private変数をインスペクターに表示カプセル化を守ったまま調整可能に
[Header]見出しでグループ化「攻撃設定」「フレーバー」の区切り
[Tooltip]ホバーで説明を表示「秒?ミリ秒?」の混乱を防ぐ
[Range]スライダー+範囲制限攻撃力9999の誤入力を物理的に不可能に
[Min]最小値制限負の弾数・0秒間隔を防ぐ
[TextArea]複数行テキスト入力説明文を1行入力欄の窮屈さから解放
[ContextMenu]右クリックでメソッド実行再生せずにインスペクターから発射テスト

適用前と後のインスペクターを見比べてください。「のっぺりしたフィールドの羅列」が「見出し・スライダー・説明つきの操作パネル」に変わります。

Attributeの有無によるインスペクターの比較図。Attributeなしでは平坦なフィールドの羅列、Header・Range・Tooltipを付けると見出し・スライダー付きの整理されたインスペクターになる

補足(Attributeは万能の守りではない): RangeMinが制限するのは インスペクターからの入力だけ です。スクリプトからattackPower = 9999;と代入すれば素通りしますし(コード側はMathf.Clampで守る)、説明文の空欄も止められません。「入力し忘れ」に気づきたい項目は、値が変わるたびエディタで呼ばれるOnValidateで警告を出すのが手軽です。

void OnValidate()
{
    if (string.IsNullOrEmpty(description))
        Debug.LogWarning($"{name}: 説明文が空です", this);
}

仕上げに、WeaponConfigのインスペクターを「数字の設定が苦手なチームメイトに渡す」つもりで触ってみてください。スライダーの外の値は入らず、空欄の説明文には警告が出る——そんな「壊しにくい操作パネル」になっていれば、この記事の主目的は達成です。ここから先は、必要になったときに引くリファレンスとして使ってください。

インスペクター拡張用のAttribute(リファレンス)

ここからはリファレンスです。上の7個を含む、インスペクター系Attributeの全体像を整理します。

SerializeField:private変数をインスペクターに表示

[SerializeField]
private float speed = 5.0f;

カプセル化を維持しながら、デザイナーやプランナーが値を調整できるようになります。

field: SerializeField:プロパティをシリアライズ

C#のauto-propertyをインスペクターに表示する現代的な書き方です。

[field: SerializeField] public float Speed { get; private set; } = 5.0f;

外部からは読み取り専用、インスペクターからは編集可能という設計が簡潔に書けます。

HideInInspector:変数をインスペクターに表示しない

[HideInInspector]
public float currentHealth;

シリアライズ自体は行われるため、値は保存されます。

NonSerialized:シリアライズを無効化

[System.NonSerialized]
public float tempValue;
Attributeシリアライズインスペクター表示
HideInInspectorされるされない
NonSerializedされないされない

使い分け: 値を保存したくない一時的な変数にはNonSerialized、保存はしたいが編集はさせたくない場合はHideInInspectorを使います。

Header:見出しを表示

[Header("移動設定")]
[SerializeField] private float moveSpeed = 5.0f;
[SerializeField] private float jumpForce = 10.0f;

[Header("攻撃設定")]
[SerializeField] private float attackPower = 20.0f;

Tooltip:ツールチップを表示

[Tooltip("移動速度(単位:m/s)")]
[SerializeField] private float moveSpeed = 5.0f;

Range:スライダーを表示

[Range(0, 100)]
[SerializeField] private float volume = 50.0f;

注意: Rangeはインスペクター上での入力制限のみです。スクリプトから直接値を代入する場合は範囲外の値も設定できてしまいます。コードからも範囲を制限したい場合はMathf.Clampを併用してください。

Min:最小値を設定

[Min(0)]
[SerializeField] private float health = 100.0f;

Space:スペースを空ける

[SerializeField] private float moveSpeed = 5.0f;
[Space(20)]
[SerializeField] private float attackPower = 20.0f;

TextArea:複数行のテキスト入力

[TextArea(3, 10)]
[SerializeField] private string description;
Sponsored

メソッド用のAttribute

ContextMenu:インスペクターのコンテキストメニューにメソッドを追加

[ContextMenu("体力を全回復")]
private void ResetHealth()
{
    currentHealth = maxHealth;
    Debug.Log("体力を全回復しました");
}

インスペクターでコンポーネントを右クリックすると、メニューが表示されます。

MenuItem:Unityエディタのメニューにメソッドを追加

重要: [MenuItem]を使用するスクリプトはEditorフォルダ 内に配置する必要があります。UnityEditor名前空間はエディタ専用のため、Editorフォルダ外に配置するとビルドエラーになります。

// Editor/MyEditorTools.cs
using UnityEditor;
using UnityEngine;

public class MyEditorTools
{
    [MenuItem("Tools/すべてのオブジェクトをリセット")]
    private static void ResetAllObjects()
    {
        Debug.Log("すべてのオブジェクトをリセットしました");
    }
}

RuntimeInitializeOnLoadMethod:ゲーム実行時に初期化

using UnityEngine;

public class GameInitializer
{
    [RuntimeInitializeOnLoadMethod]
    private static void Initialize()
    {
        Debug.Log("ゲームが起動しました");
    }
}

MonoBehaviourを継承しない静的クラスでも使用できます。

クラス用のAttribute

RequireComponent:必須コンポーネントを指定

RequireComponentの図。PlayerControllerをアタッチすると、鎖でつながったRigidbodyが自動的に一緒に付いてくる
[RequireComponent(typeof(Rigidbody))]
public class PlayerController : MonoBehaviour
{
    private Rigidbody rb;

    void Start()
    {
        rb = GetComponent<Rigidbody>();
    }
}

PlayerControllerをアタッチすると、自動的にRigidbodyもアタッチされます。さらに、PlayerControllerが付いている間はRigidbodyを削除できなくなるため、「必要なコンポーネントを外されてGetComponentがnullを返す」事故を仕組みで防げます(GetComponentの記事 参照)。

DisallowMultipleComponent:同じコンポーネントを複数アタッチできないようにする

[DisallowMultipleComponent]
public class GameManager : MonoBehaviour
{
}

ExecuteAlways:エディタモードでも実行

[ExecuteAlways]
public class GridGenerator : MonoBehaviour
{
    void Update()
    {
        // エディタモードと再生モードで処理を分ける
        if (Application.isPlaying)
        {
            // 再生中の処理
        }
        else
        {
            // エディタでの編集中の処理(シーンビューで可視化など)
        }
    }
}

注意: [ExecuteAlways]を付けると、エディタモードでもUpdateが毎フレーム呼ばれます。重い処理を書くとエディタが遅くなるため、Application.isPlayingで分岐するか、処理を軽量に保ってください。

CreateAssetMenu:ScriptableObjectの作成メニューを追加

[CreateAssetMenu(fileName = "NewWeapon", menuName = "MyGame/Weapon")]
public class WeaponData : ScriptableObject
{
    public string weaponName;
    public int damage;
}

シリアライズ用のAttribute

Serializable:カスタムクラスをシリアライズ

MonoBehaviourやScriptableObjectではないカスタムクラスをインスペクターに表示するには、[Serializable]が必要です。

[System.Serializable]
public class EnemyData
{
    public string name;
    public int hp;
    public float speed;
}

public class EnemySpawner : MonoBehaviour
{
    [SerializeField] private EnemyData[] enemies;
}

SerializeReference:ポリモーフィズム対応のシリアライズ

基底クラスやインターフェースのフィールドに、派生クラスのインスタンスを保持できます。

public interface ISkill
{
    void Execute();
}

[System.Serializable]
public class FireSkill : ISkill
{
    public float damage;
    public void Execute() { /* 火属性攻撃 */ }
}

[System.Serializable]
public class IceSkill : ISkill
{
    public float slowDuration;
    public void Execute() { /* 氷属性攻撃 */ }
}

public class Player : MonoBehaviour
{
    [SerializeReference] private ISkill skill;  // FireSkillまたはIceSkillを保持可能
}

注意: SerializeReferenceを使う場合、インスペクターでの型選択にはカスタムエディタまたはOdinInspectorなどのアセットが必要です。

Sponsored

カスタムAttributeの作成

カスタムAttributeの定義

インスペクターに反映させるカスタムAttributeを作成するには、PropertyAttributeを継承します。

using UnityEngine;

[System.AttributeUsage(System.AttributeTargets.Field)]
public class ReadOnlyAttribute : PropertyAttribute
{
}

注意: System.AttributeではなくUnityEngine.PropertyAttributeを継承する必要があります。System.Attributeを継承した場合、PropertyDrawerと連携できません。

エディタ拡張と組み合わせる

PropertyDrawerは Editorフォルダ 内に配置する必要があります。

Assets/
├── Scripts/
│   └── ReadOnlyAttribute.cs    // Attributeの定義
└── Editor/
    └── ReadOnlyDrawer.cs       // PropertyDrawer(Editorフォルダ内)
// Editor/ReadOnlyDrawer.cs
using UnityEditor;
using UnityEngine;

[CustomPropertyDrawer(typeof(ReadOnlyAttribute))]
public class ReadOnlyDrawer : PropertyDrawer
{
    public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
    {
        GUI.enabled = false;
        EditorGUI.PropertyField(position, property, label);
        GUI.enabled = true;
    }
}

重要: PropertyDrawerをEditorフォルダ外に配置するとビルドエラーになります。UnityEditor名前空間はエディタ専用のため、ビルドに含めることができません。

実践的な使用例

using UnityEngine;

[RequireComponent(typeof(Rigidbody))]
[RequireComponent(typeof(CapsuleCollider))]
public class PlayerController : MonoBehaviour
{
    [Header("移動設定")]
    [Tooltip("移動速度(単位:m/s)")]
    [SerializeField] private float moveSpeed = 5.0f;

    [Tooltip("ジャンプ力")]
    [SerializeField] private float jumpForce = 10.0f;

    [Space(20)]

    [Header("攻撃設定")]
    [Range(10, 100)]
    [SerializeField] private float attackPower = 20.0f;

    [Min(0)]
    [SerializeField] private float attackRange = 2.0f;

    [Space(20)]

    [Header("ステータス")]
    [HideInInspector]
    public float currentHealth;

    [SerializeField] private float maxHealth = 100.0f;

    [ContextMenu("体力を全回復")]
    private void ResetHealth()
    {
        currentHealth = maxHealth;
    }
}

ベストプラクティス

  • SerializeFieldとprivateを組み合わせる - カプセル化を維持しながらインスペクターで調整
  • HeaderとSpaceで見やすくする - 変数をグループ化して可読性向上
  • Tooltipで説明を追加する - チーム開発で他のメンバーが理解しやすくなる
  • RangeとMinで適切な範囲を設定する - 誤った値の設定を防止
  • RequireComponentで必須コンポーネントを指定する - 依存関係を明確にする

おまけ:先に知っておくと良いこと

Attributeに慣れてきたら、次はこのあたりが役に立ちます。

  • 変数名を変えたら値が消えた——を防ぐ [FormerlySerializedAs]: シリアライズされた変数をリネームすると、インスペクターで設定した値は 旧名と紐付いているため消えます[FormerlySerializedAs("oldName")]を付けてリネームすれば、旧名のデータを新名に引き継げます(using UnityEngine.Serialization;が必要)。チーム開発でのリネームの必須知識です。

    using UnityEngine.Serialization;
    
    // 旧名 "speed" で保存された値を "moveSpeed" に引き継ぐ
    [FormerlySerializedAs("speed")]
    [SerializeField] private float moveSpeed = 5.0f;
    
  • スクリプトの実行順を固定する [DefaultExecutionOrder]: クラスに[DefaultExecutionOrder(-100)]を付けると、Script Execution Orderの設定をコード側で指定できます。マネージャー類を先に初期化したい場合に便利です(実行順の問題は Singletonの記事 の落とし穴2参照)。

  • PropertyDrawerの先にあるエディタ拡張: カスタムAttributeで物足りなくなったら、インスペクター全体を作り替えるCustomEditorの世界です。エディタ拡張入門 で一歩踏み込めます。

まとめ

Attributeは、C#のメタデータマーカーで、コードに特別な動作を指示できます。

  • SerializeField - private変数をインスペクターに表示
  • Header、Space - インスペクターの可読性を向上
  • Range、Min - 値の範囲制限を設定
  • RequireComponent - 必須コンポーネントを自動アタッチ
  • ContextMenu - エディタでメソッドを実行可能に

ぜひ、Attributeを活用して、より読みやすく、保守しやすいコードを書いてください。

さらに学ぶために