Edited at

Unityの拡張でノードベースなウィンドウを作る

More than 3 years have passed since last update.

editor2.png


はじめに

Animator Controllerウィンドウみたいにノードを動かしたり繋げたりする拡張作りたいなーと思って調べていたら、有難いことにノードを表示して線で繋ぐサンプルを見つけた。

Simple node editor | Unity Community

やり方としては、ノード(っぽいもの)を用意して、それを結ぶ線を自前で描画するというもののようで、UnityのAPIでノードという概念が用意されているわけではないっぽい。

とりあえず作ってみてそれなりに動くものが出来たので、要点をまとめ。


ノードを描画する

まずはノードを描画するウィンドウを作成する。

これはよくある拡張と同様、 EditorWindow を継承して作成すればOK。

ウィンドウを用意したら、そのウィンドウ内の OnGUI で更にウィンドウを描画するためのメソッドを呼び出す。ウィンドウを描画するためには、以下の2つのメソッドの組が必要。

Unity - Scripting API: EditorWindow.BeginWindows

Unity - Scripting API: EditorWindow.EndWindows

このメソッドの組の中で、GUI.Window メソッドを呼び出すことで任意の矩形でウィンドウを描画できる。

Unity - Scripting API: GUI.Window

GUI.Window は第3引数にウィンドウ内のGUIを描画するための関数を取り、この中で GUI.Button 等を呼び出してGUIを描画していく。

大事なのが GUI.DragWindow の呼び出しで、このメソッドを呼び出すことでウィンドウの全体或は一部をドラッグして移動可能にすることができる。

Unity - Scripting API: GUI.DragWindow

以上のAPIを使えば以下のコードでウィンドウ内にノード(っぽいもの)を描画できる。

OnGUI 周りのコードはこんな感じになる。

Rect node = new Rect(10f, 10f, 100f, 100f);

void OnGUI()
{
BeginWindows();
node = GUI.Window(1, node, WindowCallback, "node");
EndWindows();
}

void WindowCallback(int id)
{
GUI.DragWindow();
}

node_only.png


見た目をノードっぽくする

このままの見た目ではちょっとノードっぽくないので、よりノードっぽくする。

Unityには組み込み済みのGUIのスキンがいくつかあり、それを指定することで簡単に見栄えを変更できる。

スキンの一覧は以下で紹介されているようなコードで確認可能。

Show Built In Resources - Unify Community Wiki

"flow node 5"等がよさそうなので、 GUI.Window の引数に与えてあげるといい感じになる。

Rect node1 = new Rect(10f, 10f, 100f, 100f);

Rect node2 = new Rect(10f, 10f, 100f, 100f);
void OnGUI()
{
BeginWindows();
node1 = GUI.Window(1, node1, WindowCallback, "node1", "flow node 5");
node2 = GUI.Window(2, node2, WindowCallback, "node2", "flow node 1");
EndWindows();
}

styling_node.png


ノード間を結ぶ曲線を描画する

ノード間(ウィンドウ間)を結ぶ曲線を描画する場合、それ専用のAPIが用意されている訳ではないので、"ノードが繋がっているっぽく"座標を決めて曲線を描画する必要がある。

曲線に限らず何がしかを描画したい場合、 Handles クラスが使える。

Unity - Scripting API: Handles

Handles.DrawBezier を使えばベジェ曲線を描画できる。

"node1"の右辺の中点と"node2"の左辺の中点を曲線で結ぶとこんな感じになる。

void OnGUI()

{
BeginWindows();
node1 = GUI.Window(1, node1, WindowCallback, "node1", "flow node 5");
node2 = GUI.Window(2, node2, WindowCallback, "node2", "flow node 1");

var start = new Vector3(node1.x + node1.width, node1.y + node1.height / 2f, 0f);
var startTan = start + new Vector3(100f, 0f, 0f);

var end = new Vector3(node2.x, node2.y + node2.height / 2f, 0f);
var endTan = end + new Vector3(-100f, 0f, 0f);

Handles.DrawBezier(start, end, startTan, endTan, Color.gray, null, 3f);
EndWindows();
}

bezier.png


作り込んでいくとマウスのイベントとか座標を取りたくなってくるので

EditorWindow.wantsMouseMove プロパティを真にしておく。

Unity - Scripting API: EditorWindow.wantsMouseMove

真にしておくことで、ウィンドウの上でマウスを移動するだけで OnGUI が呼ばれるようになる。

あとは Event.current.mousePosition で座標を参照したり押されたボタンを判定したりできるようになる。

ただしウィンドウの再描画は行われないようなので、例えばマウスの位置に合わせて曲線を描画するような場合は Editor.Repaint メソッドを呼び出さないと綺麗に動かない。

Unity - Scripting API: EditorWindow.Repaint


最後に

これ以上書いていくと直接的には関係ない実装をひたすら書くことになってしまうので、以上の内容を踏まえて作った拡張(記事先頭のスクリーンショット)のコードをGistに公開しておく。

NodeBaseEditor.cs

書こう、頑張って。