30
Extending the Unity Editor Unity Editor の 拡張について

Extending the Unity Editor

Embed Size (px)

Citation preview

Page 1: Extending the Unity Editor

Extending the Unity EditorUnity Editor の 拡張について

Page 2: Extending the Unity Editor

Extending the Unity Editor

2/30

まずはUnityのGUIを理解しよう (1/2)・基本的にデバッグ用

毎フレーム1つ1つのコンポーネントについて判定・再描画が行われるので重い。また使いこなすにはGUISkinなどの細かい仕様を覚えなくてはならず面倒。

・Viewport空間ではなくScreen空間に描画スクリーンに対する絶対座標で描画されるので、画面解像度が変わった時に破綻する。

・フォントの問題PC/Mac以外で日本語文字を表示しようとすると非常に面倒。携帯機ではDynamicフォントが使えない。

Page 3: Extending the Unity Editor

Extending the Unity Editor

3/30

まずはUnityのGUIを理解しよう (2/2)・OnGUI()に表示と処理を一緒くたに書く

ゲーム実行中に、ゲームオブジェクトの更新と描画が終わったタイミングでOnGUI()が呼ばれ、そこに記述されている内容が実行される。

・拡張エディタの場合もこの辺の仕組みは同じインスペクタやウィンドウの再描画が必要なタイミングで、OnInspectorGUI()やOnGUI()が呼ばれる。

void OnGUI(){

if (GUILayout.Button("Next Level")){

Application.LoadLevel("Level2");}if (GUILayout.Button("Quit")){

Application.Quit();}

}

Page 4: Extending the Unity Editor

Extending the Unity Editor

4/30

GUIとGUILayout (1/2)・GUI = 自由配置

第一引数が必ず、位置とサイズを指定するためのRect構造体。void OnGUI(){

GUI.Button(new Rect(10, 10, 100, 50), "Button1");GUI.Button(new Rect(50, 50, 100, 50), "Button2");GUI.Button(new Rect(90, 90, 100, 50), "Button3");text = GUI.TextArea(new Rect(200, 10, 100, 100), text);

}

Page 5: Extending the Unity Editor

Extending the Unity Editor

5/30

GUIとGUILayout (2/2)・GUILayout = 逐次配置

書いていった順に並んで表示される。BeginHorizontalなどでグループ化して並べることも可能。void OnGUI(){

GUILayout.BeginHorizontal(); //水平方向にグループ化GUILayout.BeginVertical(); //垂直方向にグループ化

GUILayout.Button("Button1");GUILayout.Button("Button2");GUILayout.Button("Button3");

GUILayout.EndVertical();text = GUILayout.TextArea(text, GUILayout.Width(200), GUILayout.Height(60));

GUILayout.EndHorizontal();}

※インデントは気分。必須ではない。

Page 6: Extending the Unity Editor

Extending the Unity Editor

6/30

EditorGUIとEditorGUILayout (1/2)・GUI・GUILayoutの関係と同じ

EditorGUIは絶対座標指定、EditorGUILayoutは逐次配置。Editor向けに便利なパーツが用意されている。

・GUI系とEditorGUI系は混在可能というよりEditorGUIにはButton等の基本的なパーツはなく、GUI系と混在させて使うことが前提。

public override void OnInspectorGUI(){

GUILayout.BeginHorizontal();position = EditorGUILayout.Vector3Field("Position", position);if (GUILayout.Button("Reset"))

position = Vector3.zero;GUILayout.EndHorizontal();

}

Page 7: Extending the Unity Editor

Extending the Unity Editor

7/30

EditorGUIとEditorGUILayout (2/2)・基本的にEditorGUILayoutを使用

ゲーム用のGUIがゲーム画面の左上を基準にして配置されるように、エディタ用のGUIはインスペクタやウィンドウの左上を基準にして配置されていく。座標を全部自前で計算してもいいが、EditorGUILayoutで配置しておくとスケーリングが自動で行われるのでスマート。

・位置調整用のパーツもある領域のサイズを考慮して自動的に伸縮するFlexibleSpace()や逆に勝手に伸縮しないようにするGUILayout.ExpandWidth()を上手く使ってかっこ良く配置しよう。

Page 8: Extending the Unity Editor

Extending the Unity Editor

8/30

インスペクタを拡張してみよう (1/5)・何を拡張できるの?

インスペクタに表示されるものなら何でも。選択中のオブジェクトに対する操作やインスペクタでの表示のされ方を独自に記述可能。

・コンポーネントごとに拡張クラスを定義拡張インスペクタはインスペクタ全体ではなくTransformとかCameraとかの各コンポーネント毎にクラスを定義していく。using UnityEngine; // 必須using UnityEditor; // 必須

[CustomEditor(typeof(Transform))] // Transformクラス用の拡張であることを表すアトリビュートpublic sealed class CustomTransformEditor : Editor // Editorクラスを継承する{

public override void OnInspectorGUI() // インスペクタが再描画されるタイミングで呼ばれる{

...

Page 9: Extending the Unity Editor

Extending the Unity Editor

9/30

インスペクタを拡張してみよう (2/5)・どう拡張できるの?例えば左のようなプロパティを持つコンポーネントを作ったとする。するとインスペクタでの表示は右のようになる。

これではVectorの各要素が縦に並んでいて非常に無様である。しかも初期状態では折りたたまれているし、色々ひどい。これをTransformの表示のように横に並べたい。

using UnityEngine;

public class Hoge : MonoBehaviour{

public Vector3 velocity;}

すっきり!

Page 10: Extending the Unity Editor

Extending the Unity Editor

10/30

インスペクタを拡張してみよう (3/5)・拡張エディタクラスを定義する

エディタのProject直下(フォルダ構成としてはAssets直下)にEditorという名前のフォルダを作り、そこに新規スクリプトを配置。MonoBehaviour向けに書かれている内容をEditor向けに書きなおす。

using UnityEngine; // 必須using UnityEditor; // 必須

[CustomEditor(typeof(Hoge))] // Hogeクラス用の拡張であることを宣言public sealed class HogeEditor : Editor{

public override void OnInspectorGUI(){

DrawDefaultInspector(); // とりあえずデフォルトの表示}

}

Page 11: Extending the Unity Editor

Extending the Unity Editor

11/30

インスペクタを拡張してみよう (4/5)・拡張エディタクラスを実装する

EditorGUILayoutには、そのものズバリVector3Fieldというパーツが用意されているのでそれを利用。using UnityEngine;using UnityEditor;

[CustomEditor(typeof(Hoge))]public sealed class HogeEditor : Editor{

public override void OnInspectorGUI(){

// 今インスペクタで表示しているオブジェクトのインスタンスがtargetに格納されているHoge hoge = target as Hoge; // targetプロパティはObject型なので目的の型にキャストVector3 v = EditorGUILayout.Vector3Field("Velocity", hoge.velocity);if (v != hoge.velocity) // 変更があった場合のみ処理する{

Undo.RegisterUndo(hoge, "Velocity Change"); // アンドゥバッファに登録hoge.velocity = v;EditorUtility.SetDirty(target); // アセットデータベースに変更を通知

}}

}

Page 12: Extending the Unity Editor

Extending the Unity Editor

12/30

インスペクタを拡張してみよう (5/5)・拡張エディタクラスを堪能する

Unityにフォーカスを戻してコンパイルエラーなどがなければ、インスペクタの表示が以下のように変化している。

 Undo.RegisterUndo()して あるので、Ctrl+Zで取り消し、 Ctrl+Yでやり直しもバッチリ。

Page 13: Extending the Unity Editor

Extending the Unity Editor

13/30

もっと拡張してみよう (1/2)・リセット・コピー&ペーストの実装先ほどのプロパティをゼロリセットするボタンと、数値を別のオブジェクトにコピー&ペーストするボタンを付けてみる。

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);

}}

Page 14: Extending the Unity Editor

Extending the Unity Editor

14/30

もっと拡張してみよう (2/2)・挙動の確認

Unityにフォーカスを戻してコンパイルエラーなどがなければ、インスペクタの表示が以下のように変化している。

 EditorStylesの指定や空白を 配置したことで、まとまりの ある見た目にする事ができた。

Page 15: Extending the Unity Editor

Extending the Unity Editor

15/30

他にもこんな便利なパーツが・範囲指定

EditorGUILayout.MinMaxSlider()

・ゲージ表示EditorGUI.ProgressBar()

Page 16: Extending the Unity Editor

Extending the Unity Editor

16/30

拡張インスペクタの振る舞い (1/2)・必要な時にnewされている先ほど実装した拡張インスペクタは、Unityエディタ上で対象のコンポーネントがアタッチされているオブジェクトが選択される度にnewされている。

従って、1つのオブジェクトを選択している間はインスペクタ自体のメンバプロパティは保持されるが、別のオブジェクトを選択するとインスペクタのメンバは初期値に戻る。

複数オブジェクトをまたいでやりとりしたい情報がある場合はstaticメンバを利用しよう。

またOnEnable()をオーバーライドすることで、オブジェクトを選択した瞬間の挙動を定義することも可能。

Page 17: Extending the Unity Editor

Extending the Unity Editor

17/30

拡張インスペクタの振る舞い (2/2)・必要な時にOnInspectorGUI()が呼ばれる

OnGUI()が毎フレーム呼ばれるのと違い、OnInspectorGUI()はインスペクターペインの再描画が必要になったタイミングで随時呼ばれている。

従って「一定間隔で明滅する」といったような、エディタ上でリアルタイムに何かを動かすような処理は基本的にできない。(毎回Repaint()を呼びまくれば可能ではある)

・シーンビューの更新時にはOnSceneGUI()インスペクタの親クラスであるEditorにはOnSceneGUI()というメソッドもあり、これはシーンビューが更新されるときに呼ばれている。どう使うのかというと…

Page 18: Extending the Unity Editor

Extending the Unity Editor

18/30

シーンビューも拡張してみよう (1/3)・ハンドル(マニピュレータ)の表示ベクトルを下図のようにグラフィカルに表示したい。

Page 19: Extending the Unity Editor

Extending the Unity Editor

19/30

シーンビューも拡張してみよう (2/3)・ハンドル(マニピュレータ)の表示

拡張インスペクタで取り扱っている情報を、シーンビュー上にグラフィカルに表示すると理解しやすくなる場合がある。[CustomEditor(typeof(Hoge))]public sealed class HogeEditor : Editor{

Hoge hoge = null;void OnEnable(){

hoge = target as Hoge;}void OnSceneGUI(){

Vector3 root = hoge.transform.position; // 起点を計算Vector3 cap = root + hoge.velocity; // 終点を計算Handles.color = Color.magenta; // 色を変更Handles.DrawLine(root, cap); // 線を描画Quaternion rot = Quaternion.LookRotation(hoge.velocity); // 終点の向きを計算float size = HandleUtility.GetHandleSize(cap); // カメラ距離に依らないサイズを計算Handles.ArrowCap(0, cap, rot, size); // 矢印型の終点を描画

}static Vector3 copyBuffer = Vector3.zero;public override void OnInspectorGUI()

Page 20: Extending the Unity Editor

Extending the Unity Editor

20/30

シーンビューも拡張してみよう (3/3)・ハンドル(マニピュレータ)の表示ベクトルがグラフィカルに表示されるようになった。

Page 21: Extending the Unity Editor

Extending the Unity Editor

21/30

拡張インスペクタの例

Page 22: Extending the Unity Editor

Extending the Unity Editor

22/30

ウィンドウを作ってみよう (1/5)・何を拡張できるの?

Build Settingのようなフローティングウィンドウや、AnimationウィンドウのようなDock可能なウィンドウ、あるいは簡単なウィザードなどを独自に作ることができる。

・ウィンドウごとに拡張クラスを定義カスタムウィンドウは拡張インスペクタと違い、ウィンドウ1つ1つが独立したインスタンスとして振舞う。using UnityEngine; // 必須using UnityEditor; // 必須

public sealed class MaterialReplacer : EditorWindow // EditorWindowクラスを継承する{

[MenuItem("Tool/Replace Material...")] // エディタのメニューに項目が追加されるstatic void OpenWindow() // 上記項目をクリックするとこの関数が呼ばれる{

Page 23: Extending the Unity Editor

Extending the Unity Editor

23/30

ウィンドウを作ってみよう (2/5)・Floatingウィンドウの場合

public sealed class MaterialReplacer : EditorWindow{

[MenuItem("Tool/Replace Material...")]static void OpenWindow() // メニューから呼ばれるためにはstatic関数でなければならない{

EditorWindow.GetWindow<MaterialReplacer>(true, "Replace Material");}

}

Page 24: Extending the Unity Editor

Extending the Unity Editor

24/30

ウィンドウを作ってみよう (3/5)・Dockableウィンドウの場合

public sealed class MaterialReplacer : EditorWindow{

[MenuItem("Tool/Replace Material...")]static void OpenWindow() // メニューから呼ばれるためにはstatic関数でなければならない{

EditorWindow.GetWindow<MaterialReplacer>(false, "Replace Material");}

}

Dock可能

Page 25: Extending the Unity Editor

Extending the Unity Editor

25/30

ウィンドウを作ってみよう (4/5)・OnGUI()にレイアウトを実装

拡張インスペクタと同じ要領で、OnGUI()をオーバーライドしそこへレイアウトを入れこんでいく。

[MenuItem("Tool/Replace Material...")]static void OpenWindow(){

EditorWindow.GetWindow<MaterialReplacer>(false, "Replace Material");}static Material targetMaterial = null;void OnGUI(){

targetMaterial = EditorGUILayout.ObjectField("Material", targetMaterial, typeof(Material), false

) as Material;GUI.enabled = (targetMaterial != null) && (Selection.gameObjects.Length > 0);if (GUILayout.Button("Replace!")){

foreach (var renderer in from go in Selection.gameObjects select go.renderer)if (renderer != null)

renderer.sharedMaterial = targetMaterial;}GUI.enabled = true;

}

Page 26: Extending the Unity Editor

Extending the Unity Editor

26/30

ウィンドウを作ってみよう (5/5)・ウィンドウが完成選択中のゲームオブジェクト全てのマテリアルを、指定したものに差し替えるツールが完成。

マテリアルを選択し、差し替えたいオブジェクトを選択状態にしたらReplaceボタンを押す

選択中のオブジェクトにレンダラが付いていれば、そのマテリアルが差し替わる

Page 27: Extending the Unity Editor

Extending the Unity Editor

27/30

覚えておきたい (1/4)・Selectionクラス

エディタ上で選択中のオブジェクトには、Selectionクラスを通してアクセスできる。

Page 28: Extending the Unity Editor

Extending the Unity Editor

28/30

覚えておきたい (2/4)・Undoクラス

スクリプトから変更した情報は、何もしないとUndoの履歴に残らない。エディタの拡張をする場合はUndo.RegisterUndoを有効に使い、ユーザーの利便性を図ろう。

Page 29: Extending the Unity Editor

Extending the Unity Editor

29/30

覚えておきたい (3/4)・EditorUtility、FileUtilなどのユーティリティ

EditorUtilityにはファイルオープンダイアログやYes/No形式のダイアログを表示するためのメソッド、FileUtilにはファイルのコピーや削除などを行うメソッドなど、色々便利なインターフェースが用意されている。

・スクリプトから全ての設定にアクセス可能PlayerSettingsやEditorUserBuildSettingsなどを通して、エディタ上で触れる設定項目は全てスクリプトからも操作が可能。面倒な処理はバッチ化してしまえ。

Page 30: Extending the Unity Editor

Extending the Unity Editor

30/30

覚えておきたい (4/4)・ファイル出力やシステムコールも可能

System.IO.StreamWriterを使ってgmcs.rspを書きだしたりSystem.Diagnostics.Processを使ってシステムコールを呼び出すことも可能。Unity組み込みのWWWクラスを使って簡単にウェブサーバと連携したり、可能性は無限大。