Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Extending the Unity Editor Extended

35,517 views

Published on

Extending the Unity Editor Extended

  1. 1. Extending the Unity Editor Unity Editor の 拡張について Ex
  2. 2. Extending the Unity Editor EXTENDED! そもそも何が拡張できるのか ・インスペクタの拡張 各コンポーネント(Transformなどのデフォルトコンポーネン トも含む)がインスペクタにどう表示されるかを変更できる。 ・独自ウィンドウの作成 ScriptableWizardで手軽に作ることも、EditorWindowを継 承してがっつり作ることも。ドックにはフラグ1つで対応可! ・アセットインポーターの拡張 アセットインポートの前処理・後処理を独自実装可能。 ・各種ビューの拡張 シーンビューやゲームビュー、プレビュー表示などなど。 ・各種メニューの拡張 アプリケーションメニューやコンテキストメニューなど。 2/23
  3. 3. Extending the Unity Editor EXTENDED! インスペクタの拡張 ・どんなことができるのか 表示する内容や表示する際に使用するコンポーネントを変更し たり、ユーティリティ的なボタンを追加したり。 ゲーム中でも使うGUI(Layout) クラスやEditorGUI(Layout)ク ラスに様々なパーツが用意されて いるので、それらを並べるだけで も簡単に拡張できる。 3/23
  4. 4. Extending the Unity Editor EXTENDED! インスペクタの拡張 ・どうやればできるのか(1/3) コンポーネント単位での拡張で、インスペクタの内容を変更し たいコンポーネント毎に、対応するEditorクラスを実装する。 using UnityEngine; // 必須 using UnityEditor; // 必須 [CustomEditor(typeof(Hoge))] // Hogeクラス用の拡張であることを宣言 public sealed class HogeEditor : Editor { // このメソッドをオーバーライドして処理を書いていく public override void OnInspectorGUI() { } } [CustomEditor(typeof(Transform))] // 既存のクラスを拡張することも可能 public sealed class CustomTransformEditor : Editor { public override void OnInspectorGUI() { 4/23
  5. 5. Extending the Unity Editor EXTENDED! インスペクタの拡張 ・どうやればできるのか(2/3) とりあえずOnInspectorGUI()内でDrawDefaultInspector() を呼んでおけばデフォルトの表示をやってくれる。 public override void OnInspectorGUI() { DrawDefaultInspector(); // とりあえずデフォルトの表示 } またserializedObjectプロパティを利用すればデフォルトで表 示すべき要素にひと通りアクセスできる。 public override void OnInspectorGUI() { // この書き方だと実行に支障はないのに何故かエラーが出るんだけど… foreach (SerializedProperty p in serializedObject.GetIterator()) { EditorGUILayout.PropertyField(p); } } 5/23
  6. 6. Extending the Unity Editor EXTENDED! インスペクタの拡張 ・どうやればできるのか(3/3) 要素の表示は基本的にGUILayoutクラスとEditorGUILayout クラスを利用する。 static Vector3 copyBuffer = Vector3.zero; public override void OnInspectorGUI() { Hoge hoge = target as Hoge; Vector3 v = EditorGUILayout.Vector3Field("Velocity", hoge.velocity); EditorGUILayout.BeginHorizontal(); if (GUILayout.Button("Reset", EditorStyles.miniButton)) v = Vector3.zero; GUILayout.Space(GUILayoutUtility.GetAspectRect(1).width / 3); if (GUILayout.Button("Copy", EditorStyles.miniButtonLeft)) copyBuffer = v; if (GUILayout.Button("Paste", EditorStyles.miniButtonRight)) v = copyBuffer; EditorGUILayout.EndHorizontal(); if (v != hoge.velocity) { Undo.RegisterUndo(hoge, "Velocity Change"); hoge.velocity = v; EditorUtility.SetDirty(target); } } 6/23
  7. 7. Extending the Unity Editor EXTENDED! 独自ウィンドウの作成 ・どんなことができるのか アプリケーションの「Window」メニューから開けるような ウィンドウを独自に作成することができる。 ※画像はAPARTURE Cutscene Editorの例 エディタにドックさせて常に表示しておくような物や、ダイア ログのように一時的に表示するような物など、用途に応じて。 呼び出し方は後述するメニューの拡張でアプリケーションのメ ニューに独自ウィンドウを開く項目を追加したり、拡張したイ ンスペクタに独自ウィンドウを開くためのボタンを追加した り、色々考えられる。 7/23
  8. 8. Extending the Unity Editor EXTENDED! 独自ウィンドウの作成 ・どうやればできるのか(1/4) ごく簡単なものならScriptableWizardを継承したクラスを実 装すれば、そのパブリックメンバ変数を適当に並べたウィンド ウをUnityが自動的に作ってくれる。 using UnityEngine; // 必須 using UnityEditor; // 必須 public sealed class MaterialReplacer : ScriptableWizard // ScriptableWizardクラスを継承する { public Material replaceTo; // インスペクタのようにpublicメンバが自動的に列挙される [MenuItem("Tools/Replace Material...")] // このウィザードを開くメニュー項目を追加 static void OpenWindow() { ScriptableWizard.DisplayWizard<MaterialReplacer>("Replace Material", "Apply"); } void OnWizardCreate() { // ボタンが押された時の処理をここに書いていく } } 8/23
  9. 9. Extending the Unity Editor EXTENDED! 独自ウィンドウの作成 ・どうやればできるのか(2/4) 先ほどのクラスのOnWizardCreate()を実装する。 using System.Linq; // Linqを使うので ... void OnWizardCreate() // ボタン1つの場合のデフォルト表示が「Create」なのでこんなメソッド名らしい { if (replaceTo == null) // マテリアルが選択されていなかったら何もしない return; var renderers = from r in Selection.gameObjects where r.renderer != null select r.renderer; foreach (Renderer r in renderers) r.sharedMaterial = replaceTo; } Applyボタンを押すと 選択中のオブジェクト のマテリアルが指定し たものに置き換わり、 ウィンドウが閉じる。 9/23
  10. 10. Extending the Unity Editor EXTENDED! 独自ウィンドウの作成 ・どうやればできるのか(3/4) ドックして常駐させたり、もっと凝った処理を行いたい場合は EditorWindowを継承したクラスを実装する。 using UnityEngine; // 必須 using UnityEditor; // 必須 public sealed class MaterialReplacer : EditorWindow // EditorWindowクラスを継承する { [MenuItem("Tool/Replace Material...")] // このウィンドウを開くメニュー項目を追加 static void OpenWindow() { EditorWindow.GetWindow<MaterialReplacer>(true, "Replace Material"); } void OnGUI() { // カスタムウィンドウはOnGUI()の中に表示含め処理を書いていく(インスペクタの拡張と似ている) } } 10/23
  11. 11. Extending the Unity Editor EXTENDED! 独自ウィンドウの作成 ・どうやればできるのか(4/4) 先ほどのクラスのOnGUI()を実装する。 using System.Linq; // Linqを使うので ... static Material targetMaterial = null; void OnGUI() // ScriptableWizar版とまったく同じ処理を書いてみる { targetMaterial = EditorGUILayout.ObjectField( "Replace To", targetMaterial, typeof(Material), false ) as Material; // マテリアルや対象のオブジェクトが選択されていなかったら決定できないようにしてみる GUI.enabled = (targetMaterial != null) && (Selection.gameObjects.Length > 0); if (GUILayout.Button("Apply")) { var renderers = from r in Selection.gameObjects where r.renderer != null select r.renderer; foreach (Renderer r in renderers) r.sharedMaterial = targetMaterial; Close(); } GUI.enabled = true; } 11/23
  12. 12. Extending the Unity Editor EXTENDED! アセットインポーターの拡張 ・どんなことができるのか モデルやテクスチャをインポートする時に独自の処理を加える ことができる。 またモデルインポートの際にUnityが自動で作ってくれる(作っ てしまう)マテリアルを独自のものに差し替えたり、そもそも マテリアルの生成を抑止したりもできる。 ただし個別に処理できるのがモデル・テクスチャ・サウンドだ けで、他は全てのインポートが終わった後にしかアクセスでき ないなど少し柔軟性に欠ける。 しかもそのOnPostprocessAllAssetsメソッドはstaticメ ソッドにしておかないと機能しないというトラップ付き! 12/23
  13. 13. Extending the Unity Editor EXTENDED! アセットインポーターの拡張 ・どうやればできるのか AssetPostprocessorを継承したクラスを定義し、 OnPostprocessModel()などのメソッドをオーバーライドし ていく。自作のポストプロセッサーが複数ある場合、処理順を 指定することもできる。 using UnityEngine; // 必須 using UnityEditor; // 必須 public sealed class CustomModelImporter : AssetPostprocessor { public override int GetPostprocessOrder() { return 0; // 全てのポストプロセッサーを通して、ここで返す値が小さい順に処理される } void OnPreprocessModel() { // アニメーションセットをインポートするときはマテリアルを生成しない、みたいな if (assetPath.Contains("@")) (assetImporter as ModelImporter).importMaterials = false; } } 13/23
  14. 14. Extending the Unity Editor EXTENDED! 各種ビューの拡張 ・どんなことができるのか Gizmosを使ってビューにラインやアイコンを描いたり、 Handlesを使ってハンドル(マニピュレータ)を表示したり。 上記は基本的にシーンビューへの描画だが、ゲームビューへの 描画もできる(後述)。 ハンドルは掴んで動かしてその結 果を受け取る、というところまで 面倒を見てくれるので便利。 またEditor.OnPreviewGUI()を オーバーライドすればインスペク タ下段に表示されているプレビュ 表示に手を加えることもできる。 14/23
  15. 15. Extending the Unity Editor EXTENDED! 各種ビューの拡張 ・どうやればできるのか(1/3) シーンビューに対する描画は、Editorクラスを継承してインス ペクタを拡張すると、そのインスタンスにOnSceneGUIとい うメッセージが飛んでくるのでそれを捕まえるか、 SceneViewというUndocumentedなクラスがあり、それの onSceneGUIDelegateというプロパティに適当なメソッドを 投げると、先ほどのOnSceneGUI相当の処理ができる。但し 後者は非公式。 void OnSceneGUI() { Vector3 pos = (target as Hoge).transform.position; float size = HandleUtility.GetHandleSize(pos); // カメラ距離によらないサイズの計算 Handles.DrawSolidDisc(pos, Vector3.up, size); } static HogeEditor() // デリゲートの登録は1度でいいのでstaticコンストラクタから登録してみる { SceneView.onSceneGUIDelegate += view => { Handles.DrawLine(Vector3.zero, Vector3.up); }; } 15/23
  16. 16. Extending the Unity Editor EXTENDED! 各種ビューの拡張 ・どうやればできるのか(2/3) ゲームビューに対する描画は少し特殊で、hideFlagsを HideFlags.HideAndDontSaveにしたゲームオブジェクトに ExecuteInEditMode属性を付けたコンポーネントを貼り付け るのが手っ取り早い。 // こんなコンポーネントを作っておいて [ExecuteInEditMode()] public sealed class GameViewUpdater : MonoBehaviour { void OnGUI() { // 適当な処理 } } ... // 例えばインスペクタのOnEnableで隠しゲームオブジェクトを生成し、先ほどのコンポーネントを貼り付ける void OnEnable() { GameObject go = new GameObject(); go.hideFlags = HideFlags.HideAndDontSave; go.AddComponent<GameViewUpdater>(); } 16/23
  17. 17. Extending the Unity Editor EXTENDED! 各種ビューの拡張 ・どうやればできるのか(3/3) プレビュー表示への描画は拡張インスペクタで HasPreviewGUIがtrueを返すようにオーバーライドし、 OnPreviewGUIの中に処理を書いていく。 ただし何故かレイアウトが下詰め。謎。 public override bool HasPreviewGUI () { return true; } public override void OnPreviewGUI(Rect r, GUIStyle bg) { GUILayout.Label("Label"); GUILayout.Button("Button"); } 17/23
  18. 18. Extending the Unity Editor EXTENDED! 各種メニューの拡張 ・どんなことができるのか 既に存在するアプリケーションメニュー(例えば「Window」 とか)に要素を追加することもできるし、新しいメニュー項目 を増やすこともできる。 また既存のコンテキストメニュー(インスペクタ上で右クリッ クするか、インスペクタの右上にあるちっちゃい歯車のアイコ ンを押すと出る)に要素を追加したり、独自のコンテキストメ ニューを作ったりすることもできる。 日本語は正しく処理できないっぽいです…遊んでたら落ちました 18/23
  19. 19. Extending the Unity Editor EXTENDED! 各種メニューの拡張 ・どうやればできるのか(1/3) アプリケーションメニューへの追加は、MenuItem属性を使用 する。「<項目名>/<要素名>」のようなパス形式でどこにな んというメニューを追加するか指定できる。呼び出すメソッド はどこで宣言したものでも構わない代わりに、staticでなけれ ばならない。 [MenuItem("Tools/MyMenu")] // MenuItem属性の引数でどこに追加するか指定する static void MyMenuItem() // メニューが選択されるとこのメソッドが呼ばれる { } 19/23
  20. 20. Extending the Unity Editor EXTENDED! 各種メニューの拡張 ・どうやればできるのか(2/3) コンテキストメニューへの追加は、MenuItem属性を使う方法 と、コンポーネントクラスに直接ContextMenu属性を付ける 方法がある。 MenuItem属性を使う場合はやはりメソッドはstaticでなけれ ばならないが、ContextMenu属性を使う場合はその限りでは ない。 [MenuItem("CONTEXT/Hoge/MyMenu")] // Hogeコンポーネントのコンテキストメニューに追加する場合 static void MyMenu() { } ... public class Hoge : MonoBehaviour // 上記と全く同じメニューをコンポーネントから追加する場合 { [ContexMenu("MyMenu")] void MyMenu() { } } 20/23
  21. 21. Extending the Unity Editor EXTENDED! 各種メニューの拡張 ・どうやればできるのか(3/3) マウスイベントをトラップして独自のコンテキストメニューを 表示することもできる。インスペクタでも、ウィンドウでも。 // メニューの項目が選択された時の処理をあらかじめ定義しておく(別にラムダ式でもいいけど) void Callback(Object obj) { (obj as Hoge).transform.position = Vector3.zero } // 例えばインスペクタのプレビュー表示上で右クリックされた場合に独自のコンテキストメニューを出す public override void OnPreviewGUI(Rect r, GUIStyle background) { // 飛んできたイベントをチェックして、範囲内での右クリックだったら処理する var evt = Event.current; if (evt.type == EventType.ContextClick && r.Contains(evt.mousePosition)) { var menu = new GenericMenu(); menu.AddItem(new GUIContent("Reset Position"), false, Callback, target); menu.ShowAsContext(); evt.Use(); // 自前で処理したイベントは握りつぶしておく } DrawDefaultInspector(); } 21/23
  22. 22. Extending the Unity Editor EXTENDED! ワークフローへの組み込み ・アーティスト→プログラマー アセット読み込み時の各種設定を自動化 アニメーションやコリジョンなどの編集支援 ・プログラマー→ゲームデザイナー ゲームデータ入力支援 外部データの読み込み ・プログラマー→プログラマー デバッグ支援 ビルド自動化 アセットバンドル 22/23
  23. 23. Extending the Unity Editor EXTENDED! おしまい! ご清聴ありがとうございました 23/23

×