本記事は サムザップ Advent Calendar 2025 の20日目の記事です。
はじめに
Unity の Universal Render Pipeline (URP) で Renderer Features を管理する際、標準の Inspector には以下のような課題があります。
- Renderer Features が実行順にリスト表示されるだけで、どのタイミング(RenderPassEvent)で実行されるかが直感的に把握しにくい
- ドラッグ&ドロップでの並び替えができず、順序変更が煩雑
- 複数の Feature を管理する際の視認性が低く、特にチーム開発では確認コストが高い
- URP パッケージを直接変更したくない(パッケージ更新時に変更が失われる)
- Universal Renderer Data の RendererFeatures 部分のみを拡張したい
本記事では、これらの課題を解決するためにカスタム Inspector 拡張を開発した過程と、その実装における技術的なポイントを紹介します。
完成品の概要
開発した Editor 拡張の主な機能は以下の通りです。
- RenderPassEvent ごとの自動グルーピング表示
- Unity 標準の ReorderableList によるドラッグ&ドロップ並び替え
- Foldout による詳細設定の表示/非表示
- RenderPassEvent でグループ化された Add 機能
- 完全な Undo/Redo 対応
開発の動機
標準の Inspector では、以下の点が開発効率を低下させていました。
課題1: RenderPassEvent の視認性
Feature がリスト表示されるだけで、いつ実行されるかを確認するにはコードを開く必要がありました。
課題2: 並び替えの手間
順序を変更するには右クリックメニューから "Move Up" / "Move Down" を繰り返す必要があり、複数の Feature を管理する場合は特に煩雑でした。
課題3: チーム開発での情報共有
Feature の実行タイミングが可視化されていないため、チームメンバーへの説明コストが高くなっていました。
これらの課題を解消し、より直感的な管理を実現するため、カスタム Editor 拡張の開発に至りました。
開発プロセス
本拡張は、AI(Cursor)を活用して短時間で開発しました。約1,350行の Editor スクリプトを、数時間で実装から動作確認まで完了させることができました。
AI開発での工夫点:要件の明確化
AI を効果的に活用するため、以下を意識しました。
- 機能を段階的に定義: 「ドラッグ&ドロップ」→「グループ化」→「Add機能」→「Foldout/Undo」と段階的に実装し、各段階で動作確認しながら次の要件を追加
- 制約条件を明示: 「URPパッケージ本体は変更しない」「単一ファイル実装」など、実装上の制約を事前に共有することで後からの大幅修正を回避
- 具体的なフィードバック: 「Deleteボタンで違うFeatureが削除される」など、問題を具体的に報告することでAIが原因を特定し的確な修正案を提示
機能詳細
1. RenderPassEvent によるグルーピング
各 Renderer Feature を RenderPassEvent ごとに自動分類し、折りたたみ可能なグループとして表示します。
▼ AfterRenderingOpaques (4)
- OrderVisualizer01
- OrderVisualizer02
- OrderVisualizer03
- OrderVisualizer04
▼ BeforeRenderingPostProcessing (3)
- OrderVisualizer05
- OrderVisualizer06
- OrderVisualizer06
これにより、各 Feature の実行タイミングが一目で把握できるようになりました。
2. ドラッグ&ドロップによる並び替え
Unity 標準の ReorderableList を使用することで、直感的なドラッグ&ドロップ操作を実現しています。
- 同一グループ内での自由な並び替えが可能
- 変更は即座に SerializedProperty に反映され、永続化される
- Unity 標準の UI パターンを踏襲しているため、学習コストが低い
3. 詳細設定の表示
各 Feature の詳細設定を Foldout で展開/折りたたみできるようにしました。
普段は閉じておくことで Inspector の視認性を保ちつつ、必要に応じて設定にアクセスできます。
4. Add 機能の改善
Add ボタンをクリックすると、RenderPassEvent でグループ化された Feature 選択画面(FilterWindow)が表示されます。
これにより、追加する Feature の実行タイミングを事前に把握できるようになりました。
技術的なポイント
ReorderableList の採用
当初は手動でクリック・ドラッグイベントを処理する実装を試みましたが、以下の問題が発生しました。
- クリックとドラッグの判別が困難
-
EditorGUILayout.BeginVertical()の Rect 取得タイミングの問題 - 遅延判定方式でも操作感が改善されない
そこで Unity 標準の ReorderableList に移行した結果、以下のメリットが得られました。
メリット:
- 選択・ドラッグ&ドロップ・並び替えが標準実装で提供される
- Unity の UI パターンと一貫性が保たれる
- 実装コードが約300行削減され、保守性が向上
var list = new ReorderableList(
indices,
typeof(int),
draggable: true,
displayHeader: false,
displayAddButton: false,
displayRemoveButton: false
);
// 描画・高さ計算・並び替えのコールバックを設定
list.drawElementCallback = (Rect rect, int listIndex, bool isActive, bool isFocused) => { };
list.elementHeightCallback = (int listIndex) => { };
list.onReorderCallback = (ReorderableList l) => { };
学び: 標準 API を活用することで、実装コストと保守コストの両方を削減できます。
RenderPassEvent の自動取得
カスタム Renderer Feature から RenderPassEvent を取得するため、3段階のフォールバック機構を実装しました。
private RenderPassEvent GetRenderPassEvent(ScriptableRendererFeature feature)
{
// 1. SerializedProperty で "Event" フィールドを探索
var so = new SerializedObject(feature);
var eventProp = FindRenderPassEventProperty(so);
if (eventProp != null)
return (RenderPassEvent)eventProp.intValue;
// 2. リフレクションで m_Pass から取得
var passEvent = GetRenderPassEventFromReflection(feature);
if (passEvent.HasValue)
return passEvent.Value;
// 3. 取得できない場合は Unknown グループへ
return RenderPassEvent.AfterRendering;
}
実装のポイント:
- よくあるパターン(
public RenderPassEvent Eventフィールド)は SerializedProperty で取得 - それ以外はリフレクションで
ScriptableRenderPassから取得 - どちらも失敗した場合は Unknown グループに分類
基本操作
Feature の追加:
-
Addボタンをクリック - RenderPassEvent でグループ化されたリストから選択
Feature の削除:
- 削除したい Feature をクリックして選択
-
Deleteボタンをクリック - 確認ダイアログで削除を実行
並び替え:
- グループを展開
- ドラッグハンドル(3本線)をドラッグ&ドロップ
- 同一グループ内で自由に並び替え可能
詳細設定の表示:
- Feature 項目の ▶ をクリック
- 詳細設定が展開表示される
制約事項
- グループ間の移動: 異なる RenderPassEvent 間での Feature の移動には対応していません
- RenderPassEvent の変更: Feature の実行タイミングを変更したい場合は、Feature 自体の設定を変更する必要があります
- Undo/Redo: すべての操作で Undo/Redo に対応していますが、複雑な操作の組み合わせでは稀に不整合が発生する可能性があります
コンストラクターで RenderPassEvent を設定する Feature について
現在の実装では、RenderPass のコンストラクター内で RenderPassEvent を設定している Feature は正しくグルーピングされません。これは、Editor 拡張が Feature の SerializedProperty やリフレクションで取得できる情報に依存しているためです。
メリット・デメリット
対応する場合のメリット:
- あらゆる実装パターンの Feature を正しくグルーピングできる
- より汎用的で完全な Editor 拡張になる
対応する場合のデメリット:
-
ScriptableRendererFeature.Create()を実行して RenderPass をインスタンス化する必要がある - Editor 上で Pass を生成すると、意図しない初期化処理が走る可能性がある
- パフォーマンスへの影響(Feature 数が多い場合)
実装アプローチ
もし対応する場合は、リフレクションを使って ScriptableRendererFeature.Create() メソッドを呼び出し、生成された ScriptableRenderPass インスタンスから renderPassEvent プロパティを取得する方法が考えられます。具体的には、Feature の m_Pass フィールドにアクセスして、そこから RenderPassEvent を読み取る形になります。
ただし、本実装では意図しない副作用を避けるため、この方法は採用していません。Editor 上で Create() を実行すると、Pass の初期化処理が走り、予期しない動作を引き起こす可能性があります。コンストラクターで RenderPassEvent を設定する方法は、コード上で完結して確認できるため採用しているプロジェクトも多いと思われますが、今回はリスクを考慮して見送りました。
まとめ
今回、URP の Renderer Features 管理を改善するカスタム Editor 拡張を開発しました。
標準の Inspector でも十分機能しますが、「ちょっと使いづらいな」と感じる部分を改善するだけで、日々の作業効率が大きく変わります。RenderPassEvent のグルーピングとドラッグ&ドロップによる並び替えだけでも、Inspector の視認性と操作性は段違いです。
カスタム Editor 拡張は一見ハードルが高く感じるかもしれませんが、AI を活用すれば思ったより短時間で実装できます。今回も約1,350行のコードを数時間で完成させることができました。「この操作、もっと楽にできないかな?」と感じたら、AI の力を借りて Editor 拡張にチャレンジしてみてください。
実装コードは今後公開予定です。URP を使用したプロジェクトで同じような課題を感じている方の参考になれば嬉しいです。
実装情報
リポジトリ
GitHub: (公開予定)



