littlewing

人間とコンピューターとメディアの接点をデザインするために考えたこと

Unity Bolt Tips2.Boltの独自ノードをC#で作成する

Bolt Tips 連続記事です

前回の続きです BoltのノードはUnityのC#のメソッドが利用できるのですが、自分で関数ノードを作成して再利用する事もできます。

Boltのノード作成の基礎的なメモ

MacroとEmbed

BoltのフローのソースにはMacroかEmbedを選択して利用できます。 f:id:pigshape:20200904150644p:plain

それぞれの違いは以下の通り

項目 Embed Macro
関係性 グラフはFlow Machine Component 自体に埋め込まれます グラフはMacro AssetとしてProjectに保存され、Component から参照されます。
再利用性 グラフを他のFlow Machineから再利用することはできません。
ただし、Prefab間で共有することはできます。
Component自体が削除されると、グラフも一緒に削除されます。
同じMacroを複数のFlowMachine間で共有することができます。
あるGameObjectのFlowMachineを削除した場合でも、Macro AssetはProjectに残ります。
永続性 FlowMachineの設定を、EmbedからMacroに変更するとグラフ自体が削除されます。 MacroからEmbedに切り替えてもグラフ自体はEmbedとして残ります。
Sceneの参照 Prefabとして保存されていない場合は、Embed内のグラフはScene内のGameObjectを参照することができます。 Macroの状態ではどのSceneにも属していないため、シーン内のGameObjectを参照することはできません。
Prefab Editor上でPrefabをInstanciateした場合動作しないことがある。 Editor上でもすべてのPrefab内で利用することができます。

参考: Unity Bolt - it610.com

Macroとして再利用性の高いノードをライブラリとして作成することで、効率的な開発を行うことができます。

Macro利用時のPrefabからのGameObject量産時の変数汚染

ただし、PrefabのGameObjectのFlowMachineとしてMacroを利用した場合 記述内容にも寄るかもしれませんが、 Prefabからシーンに生成されたGameObject間で変数の汚染が発生する事があります。

その場合は、PrefabのMacroをConvertボタンを押してEmbedに変換してやると、解決する場合があります。

詳細調査中なので、間違ってるかもしれません。


SuperUnit

俗にいう関数は、SuperUnitとして定義できます。

  1. 右クリック-> Add NodeでSuperUnitを作成
    f:id:pigshape:20200904152014p:plain

  2. ダブルクリックで編集できます f:id:pigshape:20200904152109p:plain

  3. SuperUnitの入り口と出口はInput/Outputを利用します。 InputノードにControl Inputs(Outputs)キーを入れる必要があります。   f:id:pigshape:20200904152148p:plain

  4. MacroをドラッグアンドドロップすることでSuperUnitとして利用することができる
    f:id:pigshape:20200904152925g:plain

  5. SuperUnitの公式のマニュアルはこちらにあります。


C#で独自Boltノードを作成する

  • C#でコードを書く
    public class HogeTest
    {
        public void TakeDamage(int damage)
        {
            Debug.Log("Damage");
        }
    }
  • SetupWizardでAssemblyを設定しておくと、ノードとして呼び出すことができます。
    f:id:pigshape:20200904153217p:plain

  • 日本語のノードも作成可能です。

public class GraphTest : MonoBehaviour
{
    public int 日本語ノード(int 体力,int 気力)
    {
        return 0;
    }
}

f:id:pigshape:20200904153953p:plainf:id:pigshape:20200904184056p:plain

using Bolt;
using Ludiq;

[UnitTitle("InOutUnit")]
[UnitCategory("Littlewing/Test")]
public class InOutUnit : Unit
{
    [DoNotSerialize]
    public ControlInput input { get; private set; }
    [DoNotSerialize]
    public ControlOutput output { get; private set; }
    [DoNotSerialize]
    public ValueInput valueIn { get; private set; }
    [DoNotSerialize]
    public ValueOutput valueOut { get; private set; }

    protected override void Definition()
    {
        input = ControlInput("in", Enter);
        output = ControlOutput("output");

        valueIn = ValueInput<float>("valueIn");
        valueOut = ValueOutput<float>("valueOut", ReturnFloat);

        Requirement(valueIn, valueOut);
        Succession(input, output);
    }

    public ControlOutput Enter(Flow flow)
    {
        return output;
    }

    public float ReturnFloat(Flow flow)
    {
        return flow.GetValue<float>(valueIn);    
    }
}

f:id:pigshape:20200904153737p:plain


Outputを複数使い分ける 分岐/非同期

ControlOutput を条件に合わせて使い分けたり、Coroutine などを利用して非同期でOutputを行いたい場合は flowのreferenceを取得して、任意のタイミングでRunを実行することで出力タイミングや、出力先を変更することができます。

f:id:pigshape:20200913102438p:plain
InOutUnit2

また、以下の例では

  • [NullMeansSelf] を利用して、TransformにデフォルトでSelfを設定
  • float value にデフォルト値0.5を設定
  • [PortLabelHidden] を利用して、入力Pin inputの名前を隠す 
  • transformvalue は 表示名称に変数名をそのまま利用
  • Succession() を利用して,Relationを明示
  • [TypeIcon] でアイコンの設定

などを行っています。

using Bolt;
using Ludiq;
using UnityEngine;

[UnitOrder(0)] 
[UnitSurtitle("Sur Title")] 
[UnitSubtitle("Sub Title")] 
[UnitTitle("InOutUnit2")]
[UnitShortTitle("Short Title")] 
[TypeIcon(typeof(Transform))]
[UnitCategory("Littlewing/Test")]
public class InOutUnit2 : Unit
{
    [DoNotSerialize]
    [PortLabelHidden]
    public ControlInput input { get; private set; }

    [PortLabel("output1")]
    [DoNotSerialize]
    public ControlOutput output1 { get; private set; }
    [PortLabel("output2")]
    [DoNotSerialize]
    public ControlOutput output2 { get; private set; }

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

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

    private Flow flow;
    private GraphReference reference;
    private Transform m_transform;
    private float m_value;
    private ControlOutput out1 => output1;
    private ControlOutput out2 => output2;

    protected override void Definition()
    {
        input = ControlInput(nameof(input), Enter);
        output1 = ControlOutput(nameof(output1));
        output2 = ControlOutput(nameof(output2));

        transform = ValueInput<Transform>(nameof(transform), null);
        value = ValueInput<float>(nameof(value), 0.5f);

        Succession(input, output1);
        Succession(input, output2);
    }

    public ControlOutput Enter(Flow flow)
    {
        this.flow = flow;
        reference = flow.stack.ToReference();
        m_transform = flow.GetValue<Transform>(transform);
        m_value = flow.GetValue<float>(value);
        bool b = true;
        if (b)
            Out1();
        else
            Out2();
        return null;
    }

    private void Out1()
    {
        Flow.New(reference).Run(out1);
        // この呼び方もできる
        //flow.Invoke(out1);
    }
    private void Out2()
    {
        Flow.New(reference).Run(out2);
        // この呼び方もできる
        //flow.Invoke(out2);
    }
}

API Reference

docs.unity3d.com


続き

littlewing.hatenablog.com