このノートでは、Unity でより読みやすく整理されたスクリプトを書くための編集テクニックをまとめます。Inspector はプランナーやアーティストが一日じゅう触るインターフェースです。ここに 1 時間かけると、何倍にもなって返ってきます。
public にせずフィールドを公開する
MonoBehaviour では多くの型の public 変数を宣言でき、その大半は Inspector から設定できます。
C#public int age;
問題は、public が「参照を持つ他のスクリプトから読み書きできる」ことも同時に意味する点です。ふつう欲しいのは Inspector の利便性であって、そのアクセス権ではありません。フィールドを private にし、[SerializeField] を付けます。
C#[SerializeField] private int age;
これでフィールドは Inspector に出たまま、他のコードからは非公開のままになります。
逆のケースもあります。シリアライズしてほしくない public フィールド——実行時に代入する、あるいはシリアライズしても意味がない——には [NonSerialized] を付けます。
C#[System.NonSerialized] public Transform runtimeTarget;
また [HideInInspector] public int index; は、public かつシリアライズされたままパネル上の行だけを隠します。他のスクリプトが書き込み、人が手で編集すべきでない値に向いています。
レイアウトを整える属性
角括弧の属性は、スクリプトの見え方を規定するためのものです。覚えておく価値があるのは次のあたりです。
C#[Header("移動")] // 上に余白を取った太字の見出し
[Tooltip("入力最大時の秒あたり移動量。")] // ホバー時に表示される説明
[Range(1, 10)] // 数値入力欄の代わりにスライダー
[Min(0)] // 下限をクランプ
[TextArea(3, 10)] // 複数行テキスト欄、最小・最大行数
[Space(12)] // 縦方向の余白(ピクセル)
[SerializeField] private float moveSpeed = 5f;
[Header] と [Space] はコストがゼロなのに、可読性への効果は他のどの変更よりも大きいです。フィールドが 20 個並んだだけのコンポーネントは使い物になりませんが、同じ 20 個を 4 つの見出しの下に分ければ十分実用になります。
もう二つ、ほぼ毎日使うものを挙げます。
C#[RequireComponent(typeof(Rigidbody))] // クラスに付ける:Unity が依存コンポーネントを自動追加
public class Mover : MonoBehaviour { }
[ContextMenu("パスをリセット")] // メソッドに付ける:コンポーネントの ⋮ メニューから実行
private void ResetPath() { }
[RequireComponent] は小さな仕掛けですが、null 参照バグを一種類まるごと防ぎます。依存コンポーネントなしにこのコンポーネントが存在することはあり得なくなり、Mover が付いている限り Unity は Rigidbody の削除を拒否します。
[ContextMenu] はもっとも安価なツールです。本来カスタムエディタを書かないとできない編集時の操作——データの再生成、地面へのスナップ、キャッシュのクリア——が、この属性を付けた private メソッド一つで済みます。
検証と派生状態
OnValidate は、エディタ上でシリアライズ値が変わるたびに呼ばれます。派生状態の整合を保つ正しい場所であり、不正な値を実行時ではなくオーサリング時に捕まえる場所でもあります。
C#private void OnValidate()
{
if (segmentCount < 1) segmentCount = 1;
if (mesh != null && mesh.isReadable == false)
Debug.LogWarning($"{name}: mesh が読み取り不可です", this);
}
OnValidate は軽く保ち、コンポーネントの外に副作用を出さないこと。ドメインリロードやプレハブのインスタンス化を含め、思っているよりずっと頻繁に呼ばれます。
カスタムエディタを書くべきとき
ここまでの属性でほとんどの場面はカバーできます。[CustomEditor] や PropertyDrawer に手を伸ばすのは、属性では本当に表現できないものが必要なときだけです。
- あるフィールドの表示可否が別のフィールドの値に依存する場合
- Scene ビューで位置や半径を直接編集するハンドルが必要な場合
- プレビュー、検証サマリ、あるいは実際に処理を行うボタンが必要な場合
多くの場合 PropertyDrawer のほうが良い選択です。ひとつのシリアライズ可能な型を対象とし、その型が現れるあらゆる場所——List の内部を含む——で機能します。一方 CustomEditor はコンポーネントのインスペクタ全体を置き換えるため、コンポーネントが育つにつれて保守コストも増えていきます。
私の判断基準はこうです。エディタコードを書く理由が自分のクリック数を減らすためなら、属性版でほぼ足ります。他人にミスをさせないためなら、カスタムドロワーを書く価値があります。