FungusをVisual Scriptingで扱うための自作ノード

# 概要

FungasにはVisualScripting同様のビジュアルスクリプティング環境がありますが、
そこをあえてVisualScriptingから扱ってみるためのノードを作ってみた。

# なんで？

- Fungusのビジュアルスクリプティング環境は基本的にFungus内の機能しか扱えない、どうにかできなくもなさそうだけど。
- Visual ScriptingはUnity公式化したので、機能的にもドキュメント的にも将来的にもよさげ

とは言っても全機能全移植は明らかに労力に見合わないので、ブロックはFungusの機能で作りそれをVisualScripting側から呼び出せるようにするのが現実的ではなかろうか。

# コルーチン

FungusのBlockはコルーチンとして実行され、すぐにVisualScriptingに制御が戻るので、何かしらの方法で終了を待つか、終了時にイベントを発行して対応する必要がある。

調べた感じUnityというかおそらくC#ではコルーチンの終了待ちはコルーチン内でしかできないようなので、終了待ちをするにはノード自体をコルーチンとして実行する必要がありそう。

というわけで終了待機版を使うにはイベントブロックのインスペクタにある「Coroutine」の項目にチェックを入れる必要があります。

# メッセージ

Fungusのブロックは開始条件として「Message Recieve」という特定の(文字列)メッセージが送られてきたら実行という設定ができるので、
任意のメッセージをFungusに送るブロックも作りました。
メッセージ送信も(複数ブロックの並列実行ができるっぽいからなのか)非同期で実行される上、ブロック実行と違って終了時のコールバックも設定できないので
終了検知は自力でFungusのブロックとVisualScriptingを連携してどうにかする必要があるようです。

# コード

```C#
using System.Collections;
using UnityEngine;
using Unity.VisualScripting;
using Fungus;

/// <summary>
/// Fungusブロックの実行(終了待機版)
/// </summary>
[UnitCategory("Fungus")]
[UnitTitle("Execute Block(Wait)")]
public class FungusBlockUnit : Unit
{
    [DoNotSerialize]
    [PortLabelHidden]
    public ControlInput InputFlow { get; private set; }

    [DoNotSerialize]
    [PortLabelHidden]
    public ControlOutput OutputFlow { get; private set; }

    [DoNotSerialize]
    [NullMeansSelf]
    public ValueInput GameObject { get; private set; }

    [DoNotSerialize]
    public ValueInput FlowChart { get; private set; }

    [DoNotSerialize]
    public ValueInput BlockName { get; private set; }

    [DoNotSerialize]
    public ValueInput EventName { get; private set; }

    [DoNotSerialize]
    public ValueOutput Executing { get; private set; }

    [DoNotSerialize]
    private bool _executing = false;

    protected override void Definition()
    {
        InputFlow = ControlInputCoroutine(nameof(InputFlow), ExecuteWait);
        OutputFlow = ControlOutput(nameof(OutputFlow));

        GameObject = ValueInput<GameObject>(nameof(GameObject), null).NullMeansSelf();
        FlowChart = ValueInput<Flowchart>(nameof(FlowChart), null);
        BlockName = ValueInput<string>(nameof(BlockName), string.Empty);
        EventName = ValueInput<string>(nameof(EventName), string.Empty);

        Executing = ValueOutput(nameof(Executing), _ => _executing);

        Requirement(BlockName, InputFlow);
        Requirement(FlowChart, InputFlow);
        Requirement(GameObject, InputFlow);

        Succession(InputFlow, OutputFlow);
    }

    public IEnumerator ExecuteWait(Flow flow)
    {
        bool complete = false;
        Flowchart flowchart = flow.GetValue<Flowchart>(FlowChart);
        GameObject gameobject = flow.GetValue<GameObject>(GameObject);
        string blockname = flow.GetValue<string>(BlockName);
        string eventname = flow.GetValue<string>(EventName);
        Block block = flowchart.FindBlock(blockname);

        if (block != null)
        {
            _executing = flowchart.ExecuteBlock(block, 0, () =>
            {
                if(eventname.CompareTo(string.Empty) != 0)
                {
                    CustomEvent.Trigger(gameobject, eventname);
                }
                complete = true;
            });

            if (_executing) yield return new WaitUntil(() => complete);
        }
        yield return OutputFlow;
    }
}

/// <summary>
/// Fungusブロックの実行(非同期版)
/// </summary>
[UnitCategory("Fungus")]
[UnitTitle("Execute Block(No Wait)")]
public class FungusBlockNoWaitUnit : Unit
{
    [DoNotSerialize]
    [PortLabelHidden]
    public ControlInput InputFlow { get; private set; }

    [DoNotSerialize]
    [PortLabelHidden]
    public ControlOutput OutputFlow { get; private set; }

    [DoNotSerialize]
    [NullMeansSelf]
    public ValueInput GameObject { get; private set; }

    [DoNotSerialize]
    public ValueInput FlowChart { get; private set; }

    [DoNotSerialize]
    public ValueInput BlockName { get; private set; }

    [DoNotSerialize]
    public ValueInput EventName { get; private set; }

    [DoNotSerialize]
    public ValueOutput Executing { get; private set; }

    [DoNotSerialize]
    private bool _executing = false;

    protected override void Definition()
    {
        InputFlow = ControlInput(nameof(InputFlow), ExecuteNoWait);
        OutputFlow = ControlOutput(nameof(OutputFlow));

        GameObject = ValueInput<GameObject>(nameof(GameObject), null).NullMeansSelf();
        FlowChart = ValueInput<Flowchart>(nameof(FlowChart), null);
        BlockName = ValueInput<string>(nameof(BlockName), string.Empty);
        EventName = ValueInput<string>(nameof(EventName), string.Empty);

        Executing = ValueOutput(nameof(Executing), _ => _executing);

        Requirement(BlockName, InputFlow);
        Requirement(FlowChart, InputFlow);
        Requirement(GameObject, InputFlow);

        Succession(InputFlow, OutputFlow);
    }

    public ControlOutput ExecuteNoWait(Flow flow)
    {
        Flowchart flowchart = flow.GetValue<Flowchart>(FlowChart);
        GameObject gameobject = flow.GetValue<GameObject>(GameObject);
        string blockname = flow.GetValue<string>(BlockName);
        string eventname = flow.GetValue<string>(EventName);
        Block block = flowchart.FindBlock(blockname);

        if (block != null)
        {
            _executing = flowchart.ExecuteBlock(block, 0, () =>
            {
                if (eventname.CompareTo(string.Empty) != 0)
                {
                    CustomEvent.Trigger(gameobject, eventname);
                }
            });
        }
        return OutputFlow;
    }
}

/// <summary>
/// Fungusメッセージの送信
/// </summary>
[UnitCategory("Fungus")]
[UnitTitle("Send Message(No Wait)")]
public class FungusSendMessage : Unit
{
    [DoNotSerialize]
    [PortLabelHidden]
    public ControlInput InputFlow { get; private set; }

    [DoNotSerialize]
    [PortLabelHidden]
    public ControlOutput OutputFlow { get; private set; }

    [DoNotSerialize]
    public ValueInput FlowChart { get; private set; }

    [DoNotSerialize]
    public ValueInput EventName { get; private set; }

    protected override void Definition()
    {
        InputFlow = ControlInput(nameof(InputFlow), SendMessage);
        OutputFlow = ControlOutput(nameof(OutputFlow));

        FlowChart = ValueInput<Flowchart>(nameof(FlowChart), null);
        EventName = ValueInput<string>(nameof(EventName), string.Empty);

        Requirement(EventName, InputFlow);

        Succession(InputFlow, OutputFlow);
    }

    public ControlOutput SendMessage(Flow flow)
    {
        Flowchart flowchart = flow.GetValue<Flowchart>(FlowChart);
        string eventname = flow.GetValue<string>(EventName);

        if(flowchart == null) 
        {
            Fungus.Flowchart.BroadcastFungusMessage(eventname);
        }
        else
        {
            flowchart.SendFungusMessage(eventname);
        }

        return OutputFlow;
    }
}

```