Unity Bolt 自作ノードの追加

はじめに
---
仕事で[Bolt](https://assetstore.unity.com/packages/tools/visual-scripting/bolt-163802?locale=ja-JP)を触る必要があったのですが、UE4に搭載されているRerouteノードのような物が存在していませんでした。
ビジュアルスクリプティングを触った事がある人なら察して頂けるかと思いますが、ビジュアルスクリプティングではすぐに言葉通りのスパゲッティコードになってしまいます。

Rerouteノードを用いると少しは改善する為、拡張機能の勉強がてら作ってみました。
Boltを扱っている人が少なく、なんとなく実装になってしまっているので、間違い等あればご指摘お願いします

![](https://i.imgur.com/Eq51nuM.png)

mossspirit/Bolt_Extensions
https://github.com/mossspirit/Bolt_Extensions

バージョン情報
---
Unity:2020.3.13
Bolt:1.4.15

Boltの自作ノードについて
---
[こちらの記事](https://littlewing.hatenablog.com/entry/2020/09/04/154251)を参考にさせて頂きました。


Boltのノードの構成については以下のようになっています。

- [Unit](https://docs.unity3d.com/bolt/1.4/api/Ludiq.Bolt.Unit.html)
    - [UnitWidget](https://docs.unity3d.com/bolt/1.4/api/Ludiq.Bolt.UnitWidget-1.html)
    - [UnitDescriptor](https://docs.unity3d.com/bolt/1.4/api/Ludiq.Bolt.UnitDescriptor-1.html)
    
Uitではそのノードが処理する実際の仕組みについて記述します。
UnitWidgetではノードのポートや見た目関連する処理UnitDescriptorではアイコンや説明文などの処理を実装します。

自分の勘違いでなければAPIに各変数やメソッド、プロパティにコメントが全くついていません。その為それっぽい関数を辿り実装していきました。

FlowRerouteノードを例に解説してみます。



```
using Ludiq;

namespace Bolt.Extensions
{
    public sealed class FlowReroute : Unit
    {
        [DoNotSerialize]
        [PortLabelHidden]
        public ControlInput input;
        [DoNotSerialize]
        [PortLabelHidden]
        public ControlOutput output;

        protected override void Definition()
        {
            input = ControlInput("in", (flow) => { return output; });
            output = ControlOutput("out");
            Succession(input, output);
        }
    }
}
```
**FlowReroute.cs**
Unitではinputに入ってきたものをそのままoutputに渡す処理を書きます

```
using Ludiq;
using UnityEngine;

namespace Bolt.Extensions
{
    [Widget(typeof(FlowReroute))]
    public sealed class FlowRerouteWidget : UnitWidget<FlowReroute>
    {
    }
}
```
**FlowRerouteWidget.cs**
Widgetの処理を書く際にはWidget(typeof(ウィジェット操作したい物のクラス名))を書き、UnitWidget<ウィジェット操作したい物のクラス名>を継承します。

```
//コンストラクタ呼び出し
public FlowRerouteWidget(FlowCanvas canvas, FlowReroute unit) : base(canvas, unit)
{
}

/// <summary>
/// 一定周期で呼ばれる
/// 他にもDraw系があるがイマイチ違いが分かっていない・・・
/// </summary>
public override void DrawForeground()
{
    //ノードの更新を行っている
    //コンストラクタで呼べば良いのでは？と思ったがダメっぽ
    GraphGUI.Node(new Rect(position.x, position.y, _position.width, _position.height), NodeShape.Square, NodeColor.Gray, isSelected);
}
```
**FlowRerouteWidget.cs**
コンストラクタとDrawForeground()を宣言します。
何個かDraw系があるのですがどれがどういった操作するのか、というのか明確にわかっていません・・・誰か教えてください。（切実）

DrawForeground()内でGraphGUIクラスのNode関数にアクセスします。ここでノードの形や位置などを設定できます。

```
/// <summary>
/// APIにコメントが書かれていないので正確ではないが、
/// ポジションの変更タイミングで呼ばれてるっぽい
/// </summary>
public override void CachePosition()
{
    //Rectのサイズを変更し、Input, Outputの矢印だけ表示している  
    _position.width = 25;
    _position.height = 25;

    //サイズを変えてRectの位置が変わってしまっているのでノード（unit)のPositionを反映
    _position.x = unit.position.x;
    _position.y = unit.position.y;

    //矢印の位置を調整
    inputs[0].y = _position.y + 5;
    outputs[0].y = _position.y + 5;
}
```
**FlowRerouteWidget.cs**
コメントで書いてある通りなのですが、CachePosition()はポジション変更のタイミングで呼ばれいるっぽいです。
この中でノードのサイズや位置、ノードのPortの位置なども設定しています。
Unitで定義したノードをWidgetでは、任意の形に変更できるようなイメージです。


```
using Ludiq;

namespace Bolt.Extensions
{
    [Descriptor(typeof(FlowReroute))]
    public sealed class FlowRerouteDescriptor : UnitDescriptor<FlowReroute>
    {
        public FlowRerouteDescriptor(FlowReroute target) : base(target)
        {
        }

        /// <summary>
        /// ポートの定義をする
        /// </summary>
        protected override void DefinedPort(IUnitPort port, UnitPortDescription description)
        {
            //Labelの表示を消す
            description.showLabel = false;
            //ノード(unit?)に反映
            base.DefinedPort(port, description);     
        }
    }
} 
```
**FlowRerouteDescriptor.cs**
DescriptorもUnitと同じように記述し、DefinedPort内でlabelを消すといった事をしています。
ちなみにここでPortの名前（label)を取得し、変更する事も可能です。


```
protected override EditorTexture DefinedIcon()
{
    return EditorTexture.Single(AssetDatabase.LoadAssetAtPath<Texture2D>("Assets/Editor/Resources/comment.png"));
}
```
**FlowRerouteDescriptor.cs**
ノードのアイコンなどを変えたい時にはDefinedIcon()宣言。EditorTexture.Single(Texture2D)を返してあげる事で、任意の画像に変更する事が可能となります。