【VRChat】UdonSharpの属性:Inspectorを読みやすくして設定ミスを減らす

作成: 2025-12-19最終更新: 2026-09-09

SerializeField・Header・Tooltip・Rangeで、ライトの設定欄を整理します。Rangeは入力を制限するだけでコードからは無視されることや、保存と同期の違いも解説します。

スクリプトを育てていくと、Inspectorの設定欄が増えてきます。数字の入力欄が7つ並んで、どれが明るさで、どれが待ち時間なのか分からなくなります。

しばらく経ってから開くと、自分でも迷います。他の人にPrefabを渡したときは、なおさらです。

この記事では、属性(Attribute)を使ってInspectorを読みやすくする 方法を解説します。コードの動きは変わりません。設定する人の見え方だけが変わります。

区切りと見出しで整理された設定パネル

この記事でわかること

  • Header・Tooltip・Rangeがそれぞれ何をするか
  • Rangeは入力を制限するだけで、コードには効かないこと
  • Inspectorに出す設定と、同期する変数の違い
  • 属性を付けすぎない目安

変数と参照 を試した状態から始めます。

Sponsored


属性は、コードに付ける付箋

属性 は、変数やメソッドの上に付ける [ ... ] の記述です。処理そのものは変えず、「これはこう扱ってほしい」という指示を添えます。

コードに付箋を貼るイメージです。付箋の内容によって、Unityの見せ方が変わります。

属性がない設定欄と、見出しやスライダーが入った設定欄の比較

よく使うのは4つです。

属性何をするか
[SerializeField]private の変数をInspectorに出す
[Header("見出し")]その位置に見出しを入れて、まとまりを作る
[Tooltip("説明")]マウスを乗せたときに説明を出す
[Range(最小, 最大)]入力欄をスライダーに変える

どれも コードの動きは変えません。 設定する人の見え方だけが変わります。それでも効果は大きく、設定ミスがはっきり減ります。

Sponsored

実践:ライトの設定欄を整理する

ライトを操作するスクリプトに、属性を足していきます。完成すると、Inspectorが3つのまとまりに分かれて、明るさはスライダーで入力できるようになります。

整理すると、まとまりが見えてスライダーで入力できる

1. 属性なしの状態を見る

まず、属性を最小限にしたコードから始めます。Assets/ScriptsLampSettings を作ります。

using UdonSharp;
using UnityEngine;

[UdonBehaviourSyncMode(BehaviourSyncMode.None)]
public class LampSettings : UdonSharpBehaviour
{
    [SerializeField] private Light targetLight;
    [SerializeField] private float dimIntensity = 0.2f;
    [SerializeField] private float brightIntensity = 2f;
    [SerializeField] private Color warmColor = new Color(1f, 0.85f, 0.6f);
    [SerializeField] private Color coolColor = Color.white;
    [SerializeField] private bool startBright;

    private bool isBright;

    private void Start()
    {
        isBright = startBright;
        Apply();
    }

    public override void Interact()
    {
        isBright = !isBright;
        Apply();
    }

    private void Apply()
    {
        if (targetLight == null) return;
        targetLight.intensity = isBright ? brightIntensity : dimIntensity;
        targetLight.color = isBright ? coolColor : warmColor;
    }
}

Cubeに付けてInspectorを見てください。6つの項目が区切りなく並んでいます。どれが明るさで、どれが色なのか、名前を読まないと分かりません。

2. Headerでまとまりを作る

いちばん効くのが Header です。変数の上に置くと、その位置に見出しが入ります。

[Header("操作するライト")]
[SerializeField] private Light targetLight;

[Header("明るさ")]
[SerializeField] private float dimIntensity = 0.2f;
[SerializeField] private float brightIntensity = 2f;

[Header("色")]
[SerializeField] private Color warmColor = new Color(1f, 0.85f, 0.6f);
[SerializeField] private Color coolColor = Color.white;

[Header("起動時の状態")]
[SerializeField] private bool startBright;

Inspectorが4つのまとまりに分かれます。同じ種類の設定が並んでいる ことが、見ただけで分かるようになりました。

見出しの数は3〜4個までにします。1項目ごとに見出しを付けると、かえって読みにくくなります。

3. Tooltipで説明を添える

名前だけでは意味が伝わらない項目に、説明を足します。

[Header("明るさ")]
[Tooltip("消灯側の明るさ。0にすると完全に消えます")]
[SerializeField] private float dimIntensity = 0.2f;

[Tooltip("点灯側の明るさ。部屋の広さに応じて調整します")]
[SerializeField] private float brightIntensity = 2f;

Inspectorでその項目にマウスを乗せると、説明が出ます。

すべての項目に付ける必要はありません。間違えやすいもの、単位が分かりにくいもの だけに絞ります。「明るさ」に「明るさです」と書いても意味がありません。

4. Rangeでスライダーにする

数値に上限と下限があるなら、スライダーにすると入力が速くなります。

[Header("明るさ")]
[Tooltip("消灯側の明るさ。0にすると完全に消えます")]
[Range(0f, 1f)]
[SerializeField] private float dimIntensity = 0.2f;

[Tooltip("点灯側の明るさ。部屋の広さに応じて調整します")]
[Range(0f, 8f)]
[SerializeField] private float brightIntensity = 2f;

Inspectorの入力欄がスライダーに変わります。ドラッグしながら、実際の明るさを見て決められます。

Playしながらスライダーを動かすと、部屋の明るさがリアルタイムで変わります。値を決めるのが一気に楽になります(Play中の変更は保存されないので、決めた値は停止後に入れ直してください)。

5. 確かめる

Inspectorを見て、4つとも変わっているか確認します。

見るところ期待する表示
項目名dimIntensityDim Intensity と自動で整形されている
まとまり「明るさ」「色」「起動時の状態」の見出しで区切られている
マウスを乗せる説明の吹き出しが出る
明るさの欄入力欄ではなくスライダーになっている

4つとも、コードの動きは1行も変わっていません。 変わったのは、あとから設定する人の手間だけです。

Rangeは入力の制限であって、実行時の制限ではない

ここが誤解しやすいところです。

Rangeはスライダーの範囲を決めるだけで、コードからの代入は制限されない

[Range(0f, 8f)] を付けても、コードからは範囲外の値を入れられます

brightIntensity = 100f;  // Rangeがあっても、これは通る

Rangeが効くのは、Inspectorの入力欄だけです。人が手で入力するときの安全装置であって、プログラムの動作を制限するものではありません。

計算した値を確実に範囲へ収めたいなら、コード側で制限します。

// 0〜8の範囲に収める
float safeIntensity = Mathf.Clamp(brightIntensity, 0f, 8f);
targetLight.intensity = safeIntensity;

Mathf.Clamp は、範囲外の値を端に切り詰める関数です。スライダーで人の入力を助け、Clampで実行時の値を守る。役割が違うので、両方使います。

Inspectorに出すことと、同期することは別

もう1つ、混同しやすい組み合わせがあります。

作る人が触るのがInspector、遊ぶ人の間でそろうのが同期
属性効果
[SerializeField]Inspectorに出す。他のプレイヤーには何も伝わらない
[UdonSynced]全員で共有する。Inspectorには出ない

[SerializeField] は「制作者が設定する値」、[UdonSynced] は「実行中に全員で共有する値」です。どちらも変数に付ける属性ですが、目的がまったく違います。

ワールドの設定(明るさの上限など)は [SerializeField]、実行中に変わる状態(いま点いているか)は [UdonSynced] になります。同期の詳しい話は ネットワーク同期入門 で扱います。

public にしてもInspectorに出ますが、これは「他のスクリプトから触ってよい」という宣言でもあります。自分で設定するだけなら [SerializeField] private を使います。

Sponsored

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

  • [Space] で余白を入れられる: 見出しを付けるほどではないが少し離したい、というときに使います
  • [TextArea] は長い文字列向け: 案内文のような複数行のテキストを入れる欄を、広く表示できます
  • [HideInInspector] は逆の働き: public だけどInspectorには出したくない、という場合に使います。他のスクリプトからは触れます
  • 属性は積み重ねられる: [Header] [Tooltip] [Range] を同じ変数に全部付けられます。順番はどれでも構いません
  • コードが読みにくくなる線引き: 属性が3行続くと、変数1つが4行になります。全部の変数に付けるのではなく、設定として重要なものだけに絞ります

まとめ

属性は、コードの動きを変えずに、設定する人の見え方を良くします。

  • Header でまとまりを作る。3〜4個まで
  • Tooltip は間違えやすい項目だけに付ける
  • Range は入力欄をスライダーにするだけ。コードからは範囲外も入る
  • 実行時に値を守りたいなら Mathf.Clamp を使う
  • [SerializeField] は設定用、[UdonSynced] は共有用。別物

付けるかどうかの問いかけは、「1か月後の自分が、この欄の意味を思い出せるか?」 です。思い出せないなら、見出しか説明を足します。

次は、処理を分けて使い回します。メソッドとカスタムイベント で、2つのボタンから同じライトを操作してみましょう。

VRChat このセクションのノート63