はじめに
xNodeでノードベースエディターを作る(導入編)の続編です。
ノードの入出力
例えばfloatの値を2つ入力して1つ出力するノードは、このようにInput,Outputアトリビュートを付けてフィールドを宣言します。
[Input(connectionType = ConnectionType.Override)] public float a;
[Input(connectionType = ConnectionType.Override)] public float b;
[Output] public float c;
いきなり文字数が多いですが、ConnectionType.Overrideを指定しているのは1つのポートに1つのノードしか繋げられないようにするためです。(…なぜデフォルト値がMultiple=複数許可なんだろう?)
そして、値を出力するためにGetValueを実装します。このノードの出力ポートに接続されているノードからこのAPIが呼ばれます。
public override object GetValue(NodePort port) {
if (port.fieldName == "c")
return GetInputValue<float>("a", a) + GetInputValue<float>("b", b);
else
return null;
}
出力ポートが複数あるノードのために、NodePort.fieldNameでポート名を判定して出力すべき内容を変えています(どの出力ポートも同じGetValueが呼ばれる)。GetInputValueはノードへの入力値を取り出すAPIですが、ノードが接続されている場合は接続されたノードの出力値、そうでない場合はノード内の入力値が取り出される仕組みになっています。
なお、未知の出力ポート名が指定された場合はnullを返すことが推奨されています。
ノードを接続しない入力
値は必ずしも他のノードから入力されるものだけでなく、直接入力させたいものもあります。その場合はInputアトリビュートを付けずにそのままpublicでフィールドを宣言します。例えば下記のようにすると、bはノード内の直接入力のみ受け付けることになります。
[Input(connectionType = ConnectionType.Override)] public float a;
public float b;
[Output(connectionType = ConnectionType.Override)] public float c;
public override object GetValue(NodePort port) {
if (port.fieldName == "c")
return GetInputValue<float>("a", a) + b;
else
return null;
}
このあたりの宣言の仕方はInspector(EditorWindow)と同じで、シリアライズしたくないフィールドはprivateやNonSerializedにするなど、いつも通りです。
複数入力
前述の通り、デフォルトでは一つのポートに複数のノードを入力できるので、ConnectionTypeは省略します。
[Input] public float InputValue;
[Output] public float OutputValue;
各入力ノードはNode.GetInputPortで取得できる入力ポートから取得することが出来ます。NodePort.GetInputValues(※複数形)で配列で値を取得したり、加算するだけならNodePort.GetInputSumを使って書けます(そんな単純な用途はまずないとは思うけど)。
public override object GetValue(NodePort port) {
if (port.fieldName == "OutputValue")
return GetInputPort("InputValue").GetInputSum(InputValue);
else
return null;
}
これだとユーザーが好きな数だけノードを繋げられるので、接続する数を2つとかに固定したい場合は、同じ型のポートを必要な分だけ複数宣言してConnectionType.Overrideにして下さい。
表示するノード
ノードの実装だけでは入力値や出力値を画面に表示することができません(マウスオーバーで表示できるのは置いといて)。表示するためには各ノードに対応する xNode.NodeEditor の派生クラスを作って表示部をカスタマイズする必要があります。カスタムインスペクターと同じような仕組みです。
NodeEditorはAssetsメニューにはないので、普通にC# Scriptを生成して書き換えてください。
using UnityEngine;
using XNode;
public class DisplayNode : Node
{
[Input(ShowBackingValue.Never, ConnectionType.Override)] public float input;
public object GetResult()
{
return GetInputValue<object>("input");
}
}
using UnityEditor;
using XNodeEditor;
[CustomNodeEditor(typeof(DisplayNode))]
public class DisplayNodeEditor : NodeEditor
{
public override void OnBodyGUI()
{
base.OnBodyGUI();
var displayNode = target as DisplayNode;
var obj = displayNode.GetResult();
if (obj != null) EditorGUILayout.LabelField("Value", obj.ToString());
}
}
値を持たないノード
さて、a+b=cのようなノードはこれで作れるようになりましたが、繋げたノードを順に実行するといった、値が必要ない用途の場合はポートの型を何にすべきでしょうか? 公式サンプルによると、何もしないクラスを定義してInput,Outputとして宣言するのが定石のようです。
[System.Serializable]
public class Empty { }
[Input(ShowBackingValue.Never)] public Empty Input;
[Output] public Empty Output;
クラスをSerializableにするのがポイントです。ここでInputを"ShowBackingValue.Never"としているのは入力欄を表示しないためで、そもそもダミーのクラスは値がなく手入力できないので非表示にします。OutputはNeverがデフォルト値なので指定していません(詳細はxNodeのコードを参照のこと)。
GetValueは実装しなくてもかまいません(nullが返ります)。
おわりに
これでノードベースエディターの基本的な仕組みは実装できるようになったはずです。しかし、ここまでではノードグラフが出来たというだけで、ゲーム(ランタイム)で使う方法を実装しないといけません。次はそのあたりを考えていきます。ここからがわりと悩ましい…。