Extending the Unity EditorUnity Editor の 拡張について Ex
Extending the Unity Editor EXTENDED!
2/23
そもそも何が拡張できるのか・インスペクタの拡張
各コンポーネント(Transformなどのデフォルトコンポーネントも含む)がインスペクタにどう表示されるかを変更できる。
・独自ウィンドウの作成ScriptableWizardで手軽に作ることも、EditorWindowを継承してがっつり作ることも。ドックにはフラグ1つで対応可!
・アセットインポーターの拡張アセットインポートの前処理・後処理を独自実装可能。
・各種ビューの拡張シーンビューやゲームビュー、プレビュー表示などなど。
・各種メニューの拡張アプリケーションメニューやコンテキストメニューなど。
Extending the Unity Editor EXTENDED!
3/23
インスペクタの拡張・どんなことができるのか
表示する内容や表示する際に使用するコンポーネントを変更したり、ユーティリティ的なボタンを追加したり。
ゲーム中でも使うGUI(Layout)クラスやEditorGUI(Layout)クラスに様々なパーツが用意されているので、それらを並べるだけでも簡単に拡張できる。
Extending the Unity Editor EXTENDED!
4/23
インスペクタの拡張・どうやればできるのか(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(){
Extending the Unity Editor EXTENDED!
5/23
インスペクタの拡張・どうやればできるのか(2/3)
とりあえずOnInspectorGUI()内でDrawDefaultInspector()を呼んでおけばデフォルトの表示をやってくれる。
またserializedObjectプロパティを利用すればデフォルトで表示すべき要素にひと通りアクセスできる。
public override void OnInspectorGUI(){
DrawDefaultInspector(); // とりあえずデフォルトの表示}
public override void OnInspectorGUI(){
// この書き方だと実行に支障はないのに何故かエラーが出るんだけど…foreach (SerializedProperty p in serializedObject.GetIterator()){
EditorGUILayout.PropertyField(p);}
}
Extending the Unity Editor EXTENDED!
6/23
インスペクタの拡張・どうやればできるのか(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);
}}
Extending the Unity Editor EXTENDED!
7/23
独自ウィンドウの作成・どんなことができるのか
アプリケーションの「Window」メニューから開けるようなウィンドウを独自に作成することができる。
エディタにドックさせて常に表示しておくような物や、ダイアログのように一時的に表示するような物など、用途に応じて。呼び出し方は後述するメニューの拡張でアプリケーションのメニューに独自ウィンドウを開く項目を追加したり、拡張したインスペクタに独自ウィンドウを開くためのボタンを追加したり、色々考えられる。
※画像はAPARTURE Cutscene Editorの例
Extending the Unity Editor EXTENDED!
8/23
独自ウィンドウの作成・どうやればできるのか(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(){// ボタンが押された時の処理をここに書いていく}
}
Extending the Unity Editor EXTENDED!
9/23
独自ウィンドウの作成・どうやればできるのか(2/4)
先ほどのクラスのOnWizardCreate()を実装する。
Applyボタンを押すと選択中のオブジェクトのマテリアルが指定したものに置き換わり、ウィンドウが閉じる。
using System.Linq; // Linqを使うので...
void OnWizardCreate() // ボタン1つの場合のデフォルト表示が「Create」なのでこんなメソッド名らしい{
if (replaceTo == null) // マテリアルが選択されていなかったら何もしないreturn;
var renderers = from r in Selection.gameObjectswhere r.renderer != nullselect r.renderer;
foreach (Renderer r in renderers)r.sharedMaterial = replaceTo;
}
Extending the Unity Editor EXTENDED!
10/23
独自ウィンドウの作成・どうやればできるのか(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()の中に表示含め処理を書いていく(インスペクタの拡張と似ている)}
}
Extending the Unity Editor EXTENDED!
11/23
独自ウィンドウの作成・どうやればできるのか(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.gameObjectswhere r.renderer != nullselect r.renderer;
foreach (Renderer r in renderers)r.sharedMaterial = targetMaterial;
Close();}GUI.enabled = true;
}
Extending the Unity Editor EXTENDED!
12/23
アセットインポーターの拡張・どんなことができるのかモデルやテクスチャをインポートする時に独自の処理を加えることができる。またモデルインポートの際にUnityが自動で作ってくれる(作ってしまう)マテリアルを独自のものに差し替えたり、そもそもマテリアルの生成を抑止したりもできる。
ただし個別に処理できるのがモデル・テクスチャ・サウンドだけで、他は全てのインポートが終わった後にしかアクセスできないなど少し柔軟性に欠ける。しかもそのOnPostprocessAllAssetsメソッドはstaticメソッドにしておかないと機能しないというトラップ付き!
Extending the Unity Editor EXTENDED!
13/23
アセットインポーターの拡張・どうやればできるのか
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;}
}
Extending the Unity Editor EXTENDED!
14/23
各種ビューの拡張・どんなことができるのか
Gizmosを使ってビューにラインやアイコンを描いたり、Handlesを使ってハンドル(マニピュレータ)を表示したり。上記は基本的にシーンビューへの描画だが、ゲームビューへの描画もできる(後述)。
ハンドルは掴んで動かしてその結果を受け取る、というところまで面倒を見てくれるので便利。
またEditor.OnPreviewGUI()をオーバーライドすればインスペクタ下段に表示されているプレビュ表示に手を加えることもできる。
Extending the Unity Editor EXTENDED!
15/23
各種ビューの拡張・どうやればできるのか(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);
};}
Extending the Unity Editor EXTENDED!
16/23
各種ビューの拡張・どうやればできるのか(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>();
}
Extending the Unity Editor EXTENDED!
17/23
各種ビューの拡張・どうやればできるのか(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");
}
Extending the Unity Editor EXTENDED!
18/23
各種メニューの拡張・どんなことができるのか
既に存在するアプリケーションメニュー(例えば「Window」とか)に要素を追加することもできるし、新しいメニュー項目を増やすこともできる。
また既存のコンテキストメニュー(インスペクタ上で右クリックするか、インスペクタの右上にあるちっちゃい歯車のアイコンを押すと出る)に要素を追加したり、独自のコンテキストメニューを作ったりすることもできる。
日本語は正しく処理できないっぽいです…遊んでたら落ちました
Extending the Unity Editor EXTENDED!
19/23
各種メニューの拡張・どうやればできるのか(1/3)
アプリケーションメニューへの追加は、MenuItem属性を使用する。「<項目名>/<要素名>」のようなパス形式でどこになんというメニューを追加するか指定できる。呼び出すメソッドはどこで宣言したものでも構わない代わりに、staticでなければならない。
[MenuItem("Tools/MyMenu")] // MenuItem属性の引数でどこに追加するか指定するstatic void MyMenuItem() // メニューが選択されるとこのメソッドが呼ばれる{}
Extending the Unity Editor EXTENDED!
20/23
各種メニューの拡張・どうやればできるのか(2/3)
コンテキストメニューへの追加は、MenuItem属性を使う方法と、コンポーネントクラスに直接ContextMenu属性を付ける方法がある。MenuItem属性を使う場合はやはりメソッドはstaticでなければならないが、ContextMenu属性を使う場合はその限りではない。
[MenuItem("CONTEXT/Hoge/MyMenu")] // Hogeコンポーネントのコンテキストメニューに追加する場合static void MyMenu(){}
...public class Hoge : MonoBehaviour // 上記と全く同じメニューをコンポーネントから追加する場合{
[ContexMenu("MyMenu")]void MyMenu(){}
}
Extending the Unity Editor EXTENDED!
21/23
各種メニューの拡張・どうやればできるのか(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();
}
Extending the Unity Editor EXTENDED!
22/23
ワークフローへの組み込み・アーティスト→プログラマー
アセット読み込み時の各種設定を自動化アニメーションやコリジョンなどの編集支援
・プログラマー→ゲームデザイナーゲームデータ入力支援外部データの読み込み
・プログラマー→プログラマーデバッグ支援ビルド自動化アセットバンドル
Extending the Unity Editor EXTENDED!
23/23
おしまい!
ご清聴ありがとうございました