ポートとトリガー
ポートとトリガーはエンティティに属し、Warudoスクリプティングで最も重要な要素と言えるでしょう。エンティティ間でデータを渡し、アクションをトリガーし、エディターでユーザー操作を 提供するために使用します。


データ入力ポート
データ入力ポートは、ユーザー(エディターを使用)または別のエンティティからエンティティへデータを渡すために使用します。データ入力には文字列、数値、ブール値のほか、構造化データや配列などの複雑な型も使用できます。
データ入力は、[DataInput]属性で修飾されたエンティティサブクラスのパブリックフィールドとして定義します。初めてのスクリプトを作成するの例では、数値スライダーを定義するDataInputを確認しました。
[DataInput]
[IntegerSlider(1, 100)]
public int LuckyNumber = 42;
さらにいくつか例を示します。
-
文字列入力:
[DataInput]
public string MyName = "Alice"; -
列挙型入力:(エディターではドロップダウンとして表示)
public enum Color {
Red,
Green,
Blue
}
[DataInput]
public Color MyColor = Color.Red; -
配列入力:(エディターでは編集可能なリストとして表示)
[DataInput]
public float[] MyFavoriteNumbers = new float[] { 3.14f, 2.718f };
エディターでは次のように表示されます(ここではノードを使用していますが、これらのデータ入力はアセットやプラグインでも同じように機能します)。

コードで指定した既定値で初期化されていることに注目してください。ユーザーがデータ入力ラベルの横にある「リセット」ボタンをクリックすると、これらの既定値が再びデータ入力に代入されます。
通常、データ入力にはシリアライズ可能な型を使用します(ここでの「シリアライズ可能」とは、Warudoを閉じて再度開いたときにデータ値を保存・復元できることを意味します)。次の型は既定でシリアライズ可能です。
- プリミティブ型:
int、float、bool、string、任意の列挙型 - Unity型:
Vector2、Vector3、Vector4、Color - 構造化データ型
- アセット参照
- シリアライズ可能な型の配列
ノードでは、エディターで編集できない代わりにノード自身が処理する、シリアライズ不可能なデータ入力を定義できます。たとえば、次のコードはジェネリックなobjectデータ入力に対してToString()メソッドを呼び出します。
[NodeType(Id = "dc28819e-5149-4573-945e-40e81e2874c4", Title = "ToString()", Category = "CATEGORY_ARITHMETIC")]
public class ToStringNode : Node {
[DataInput]
public object A; // Not serialized
[DataOutput]
[Label("OUTPUT_STRING")]
public string Result() {
return A?.ToString();
}
}
Dictionary、GameObject、Quaternionも、シリアライズできませんがノード間で受け渡しできる一般的なデータ入力型です。
属性
データ入力ポートには属性を付与して、エディターに追加の情報を与えられます。たとえば、先ほどの[IntegerSlider]属性は、データ入力を1から100の範囲を持つスライダーとして表示するよう指定しています。対応する属性は次のとおりです。
[Label(string label)]: データ入力のラベルを指定します。[HideLabel]: データ入力のラベルを非表示にするよう指定します。[Description(string description)]: データ入力の説明を指定します。[HiddenIf(string methodName)]: 指定したメソッドがtrueを返す場合に、データ入力を非表示にするよう指定します。メソッドはエンティティクラス内のboolを返すpublicまたはprotectedメソッドである必要があります。[DataInput]
public int MyNumber = 0;
[DataInput]
[HiddenIf(nameof(IsSecretDataInputHidden))]
public string SecretDataInput = "I am hidden unless MyNumber is 42!";
public bool IsSecretDataInputHidden() => MyNumber != 42;[HiddenIf(string dataInputPortName, If @if, object value)]: 指定したデータ入力ポートが@if条件を満たす場合に、データ入力を非表示にするよう指定します。値は定数でなければなりません。[DataInput]
public int MyNumber = 0;
[DataInput]
[HiddenIf(nameof(MyNumber), If.NotEqual, 42)]
public string SecretDataInput = "I am hidden unless MyNumber is 42!";[HiddenIf(string dataInputPortName, Is @is)]: 指定したデータ入力ポートが@is条件を満たす場合に、データ入力を非表示にするよう指定します。[DataInput]
public CharacterAsset MyCharacter;
[DataInput]
[HiddenIf(nameof(MyCharacter), Is.NullOrInactive)]
public string SecretDataInput = "I am hidden unless MyCharacter is selected and active!";[DisabledIf(...)]:[HiddenIf(...)]と似ていますが、データ入力を非表示ではなく無効化します。[Hidden]: データ入力を常に非表示にするよう指定します。コード内でデータ入力を使用したいものの、ユーザーには公開したくない場合に便利です。[Disabled]: データ入力を常に無効(編集不可)にするよう指定します。長さが固定の配列データ入力や、常にプログラムから設定されるデータ入力を表示する場合などに便利です。[Section(string title)]: 別のセクションが指定されない限り、データ入力および以降のすべてのデータ入力を、指定したタイトルの新しいセクションに表示するよう指定します。[SectionHiddenIf(string methodName)]:[Section]属性が必要です。指定したメソッドがtrueを返す場合に、そのセクションを非表示にするよう指定します。メソッドはエンティティクラス内のboolを返すpublicまたはprotectedメソッドである必要があります。@ifおよび@is条件もサポートされています。[Markdown(bool Primary = false)]: データ入力をMarkdownテキストとして表示し、編集できないようにすることを指定します。データ入力はstring型でなければなりません。Primaryがtrueの場合、テキストは色付き背景なしで大きなフォントサイズで表示されます。詳細はテキストブロックを参照してください。
[HiddenIf]および[DisabledIf]属性は、アセットまたはノードがエディターで表示されている間、毎フレーム評価されます。そのため、これらのメソッドでは負荷の高い処理を避けてください。
一部の属性は、特定のデータ入力型に固有です。
[IntegerSlider(int min, int max, int step = 1)]: データ型にintまたはint[]が必要です。データ入力を指定範囲の整数スライダーとして表示するよう指定します。[FloatSlider(float min, float max, float step = 0.01f)]: データ型にfloatまたはfloat[]が必要です。データ入力を指定範囲の浮動小数点スライダーとして表示するよう指定します。[AutoCompleteResource(string resourceType, string defaultLabel = null)]: データ型にstringが必要です。データ入力を指定した型のリソースのオートコンプリートリストとして表示するよう指定します。たとえば、「キャラクター → デフォルトアイドルアニメーション」データ入力は[AutoCompleteResource("CharacterAnimation")]として定義されています。詳細はリソースプロバイダーとリゾルバーページを参照してください。[AutoCompleteList(string methodName, bool forceSelection = false, string defaultLabel = null)]: データ型にstringが必要です。データ入力を、指定したメソッドで生成されるドロップダウンメニューとして表示するよう指定します。メソッドはエンティティクラス内のUniTask<AutoCompleteList>を返すpublicまたはprotectedメソッドである必要があります。このメソッドは非同期にできます。forceSelectionがtrueの場合、ユーザー はドロップダウンリストからのみ値を選択でき、それ以外の場合は値にnullが代入されます。using System.Linq;
[DataInput]
[AutoComplete(nameof(AutoCompleteVipName), forceSelection: true)]
public string VipName = "Alice";
protected async UniTask<AutoCompleteList> AutoCompleteVipName() {
return AutoCompleteList.Single(vipNames.Select(name => new AutoCompleteEntry {
label = name, // This is what the user sees
value = name // This is what the field stores
}).ToList());
}
private List<string> vipNames; // Entity-controlled runtime data
// Some other code should update the vipNames listヒントオートコンプリートリストは、コンパイル時には分からない選択肢のリストを提供したい場合に便利です。たとえば、ディレクトリ内のファイル一覧やリモートサーバー上のエモート一覧を提供できます。Warudoの内部ノードやアセットでは非常に頻繁に使用されています。
[MultilineInput]: データ型にstringが必要です。データ入力を複数行のテキスト入力フィールドとして表示するよう指定します。[CardSelect]: 列挙型のデータ型が必要です。データ入力をカード選択リスト(「カメラ → コントロールモード」に類似)として表示するよう指定します。public enum Color {
[Label("#00FF00")]
[Description("I am so hot!")]
Red,
[Label("#00FF00")]
[Description("I am so natural!")]
Green,
[Label("#0000FF")]
[Description("I am so cool!")]
Blue
}
[DataInput]
[CardSelect]
public Color MyColor = Color.Red;
列挙型
列挙型で[Label(string label)]属性を使用すると、列挙値のエディター上のラベルをカスタマイズできます。例:
public enum Color {
[Label("#FF0000")]
Red,
[Label("#00FF00")]
Green,
[Label("#0000FF")]
Blue
}
属性セクションで説明したように、[Description(string description)]および[Icon(string icon)]属性を使用して、列挙型入力をカードのリストとして表示することもできます。
iconは単一のSVG要素(例: <svg>...</svg>)にしてください。例:
<svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 512 512">
<path>...</path>
</svg>
アセット参照
ア セット参照は、アセット、ノード、およびプラグイン以外の構造化データでのみ使用できます。
データ入力は、現在のシーンにある別のアセットを参照するために使用できます。たとえば、次のコードはCharacterAssetを参照するデータ入力を定義します。
[DataInput]
public CharacterAsset MyCharacter;
エディターでは、ユーザーはドロップダ ウンリストからキャラクターアセットを選択できます。

その後、参照先アセットのポートとトリガーにアクセスできます。
[FlowInput]
public Continuation Enter() {
if (MyCharacter.IsNonNullAndActive()) {
MyCharacter.EnterExpression("Joy", transient: true); // Make the character smile!
}
return Exit;
}
asset.IsNullOrInactive()を使用するとアセットがnullまたは非アクティブかどうかを確認でき、逆にasset.IsNonNullAndActive()を使用するとnullではなくアクティブかどうかを確認できます。
ドロップダウンに表示するアセットのリストをフィルタリングしたい場合はどうすればよいでしょうか。[AssetFilter(string methodName)]属性を使用して、シーン内のアセットをフィルタリングするメソッドを指定できます。メソッドはアセット型のパラメーターを受け取り、boolを返すpublicまたはprotectedメソッドである必要があります。例:
[DataInput]
[AssetFilter(nameof(FilterCharacterAsset))]
public CharacterAsset MyCharacter;
protected bool FilterCharacterAsset(CharacterAsset character) {
return character.Active; // Only show active characters
}
プログラムからデータ入力にアクセスする
エンティティがあるとします。データ入力を読み取る方法は2つあります。
- データ入力フィールドへ直接アクセスします。たとえば、ノードにpublicなデータ入力フィールド
public int MyNumber = 42;がある場合、node.MyNumberを使用してMyNumberの値を直接読み取れます。 T GetDataInput<T>(string key)またはobject GetDataInput(string key)メソッドを使用します。このメソッドはすべてのエンティティで使用でき、指定名のデータ入力の値を返します。たとえば、MyNumberというデータ入力を持つノードでは、node.GetDataInput<int>("MyNumber")を使用してMyNumberの値を読み取れます(スタイル上はnode.GetDataInput<int>(nameof(node.MyNumber))が推奨されます)。
ポートのキーは、ポートが動的に追加された場合を除き、常にフィールド名です(動的ポートを参照)。
2つ目の方法は、文字列変数に基づいてデータ入力へアクセスするなど、データ入力に動的にアクセスする必要がある場合に便利です(動的ポートも参照)。それ以外では、2つの方法に実用上の違いはありません。
同様に、データ入力に書き込むには、データ入力フィールドへ直接値を代入するか、void SetDataInput<T>(string key, T value)メソッドを使用します。たとえば、MyNumberというデータ入力の値を設定するには、node.MyNumber = 42またはnode.SetDataInput("MyNumber", 42, broadcast: true)を使用できます(スタイル上はnode.SetDataInput(nameof(node.MyNumber), 42, broadcast: true)が推奨されます)。
SetDataInput(nameof(MyNumber), 42, broadcast: true)の別の書き方は次のとおりです。
MyNumber = 42;
BroadcastDataInput(nameof(MyNumber));
ただし、この場合は次の2つの理由により、2つ目の方法を強く推奨します。
- このデータ入力のウォッチャーに変更が通知されることを保証します。
broadcastパラメーターをtrueに設定すると、変更がエディターに送信されます。設定しない場合は、BroadcastDataInput(string key)を使用して手動で変更をエディターへ送信する必要があります。
次の場合にのみ、1つ目の方法を使用してください。
- データ入力を非常に頻繁に更新し、すべての変更をエディターへ送信する必要がない場合。つまり、
BroadcastDataInput(string key)を断続的に呼び出す場合です。これにより処理負荷を抑えられます。 - このデータ入力のウォッチャーに明示的に通知したくない場合。これはまれです。
データ出力ポート
データ出力ポートはノード固有で、他のノードへデータを渡すために使用します。データ出力は、[DataOutput]属性で修飾されたノードサブクラスのパブリックメソッドとして定義します。メソッドはvoid以外の任意の型を返せます。例:
[DataOutput]
public int RandomNumber() {
return Random.Range(1, 100); // Return a random number between 1 and 100
}
データ出力では、データ入力属性の一部である[Label]、[HideLabel]、[Description]、[HiddenIf]、[DisabledIf]をサポートしています。
フロー入力ポート
フロー入力ポートはノード固有で、他のノードからフロー信号を受け取り、特定のアクションをトリガーするために使用します。フロー入力は、[FlowInput]属性で修飾されたノードサブクラスのパブリックメソッドとして定義します。メソッドはフロー出力Continuationを返す必要があります。例:
[DataInput]
public bool FlowToA = true;
[FlowInput]
public Continuation Enter() {
return FlowToA ? ExitA : ExitB; // If FlowToA is true, trigger ExitA; otherwise, trigger ExitB
}
[FlowOutput]
public Continuation ExitA;
[FlowOutput]
public Continuation ExitB;
フロー入力では、データ入力属性の一部である[Label]、[HideLabel]、[Description]をサポートしています。メソッド名がEnter()で[Label]属性がない場合、ラベルはエディターの言語に合わせて「Enter」に自動設定されます。
フロー出力ポート
フロー出力ポートはノード固有で、他のノードへフロー信号を送るために使用します。フロー出力は、[FlowOutput]属性で修飾されたノードサブクラスのパブリックフィールドとして定義します。フィールドはContinuation型である必要があります。例についてはフロー入力ポートを参照してください。
フロー出力では、データ入力属性の一部である[Label]、[HideLabel]、[Description]をサポートしています。フィールド名がExitで[Label]属性がない場合、ラベルはエディターの言語に合わせて「Exit」に自動設定されます。
テキストブロック
テキストブロックは、[Markdown]属性で修飾されたエンティティサブクラスのパブリック文字列フィールドとして定義します。
テキストブロックには2つのスタイルがあります。
- 背景あり:
[Markdown] - 背景なし:
[Markdown(Primary = true)]
テキストブロックは基本的なMarkdown構文をサポートし、厳密な改行モードを使用します。つまり:
\nが1つの場合は改行を作らず、スペースのように扱われます。\nの前に2つのスペースがある場合(すなわち␠␠\n)、新しい段落を開始せずに改行します。\nが2つ連続する場合(すなわち\n\n)、改行して新しい段落を開始します。
テキストブロックは、次のような埋め込みHTMLもサポートしています。
[Markdown]
public string TextWithHtml = "Hello <p style='color: red;'>Hello</p> Hello";
文字列変数を変更してからBroadcastDataInputを使用すると、テキストブロックの表示を更新できます。
MarkdownVariable = "New Text";
BroadcastDataInput(nameof(MarkdownVariable));
テキストブロックの更新には、OnCreate()内でWatchまたはWatchAllを使用することを推奨します。
必要な場合にのみOnUpdate()でテキストブロックを更新してください。更新するとパフォーマンスのオーバーヘッドが大きくなります。
注: [HiddenIf(string methodName)]などのDataInputの属性もテキストブロックに適用されます。
詳細な例を示します。
using UnityEngine;
using Warudo.Core.Attributes;
using Warudo.Core.Graphs;
[NodeType(
Id = "Markdown-Example-Node",
Title = "Markdown Example Node",
Category = "Examples")]
public class MarkdownExampleNode : Node {
[Markdown]
public string Markdown = "### Title\n\nHello1\nHello2 \nHello3\n\n<p style='color: red;'>Hello4</p>\n\n- list1\n- list2\n\n**bold** *italic* `code`";
[Markdown(Primary = true)]
public string MarkdownPrimary = "### Title\n\nHello1\nHello2 \nHello3\n\n<p style='color: red;'>Hello4</p>\n\n- list1\n- list2\n\n**bold** *italic* `code`";
[DataInput]
[FloatSlider(0, 1)]
public float A = 0.5f;
[DataInput]
[FloatSlider(0, 1)]
public float B = 0.5f;
[Markdown(Primary = true)]
public string MarkdownDynamicPrimary = "A: 0.5 \nB: 0.5";
protected override void OnCreate() {
base.OnCreate();
WatchAll(new[] {
nameof(A),
nameof(B),
}, () => {
MarkdownDynamicPrimary = "A: " + A.ToString() + " \nB: " + B.ToString();
BroadcastDataInput(nameof(MarkdownDynamicPrimary));
});
}
[Markdown]
public string MarkdownDynamic = "";
public override void OnUpdate() {
base.OnUpdate();
string time = "RealTime: " + Time.time.ToString();
MarkdownDynamic = time;
BroadcastDataInput(nameof(MarkdownDynamic));
}
}
トリガー
トリガーは簡単に言えば、エディターでクリックして特定のアクションをトリガーできるボタンです。トリガーは 、[Trigger]属性で修飾されたエンティティサブクラスのパブリックメソッドとして定義します。例:
[Trigger]
public void ShowPopupMessage() {
Context.Service.PromptMessage("Title of the message", "Content of the message");
}
エディターでは次のように表示されます。

ユーザーがボタンをクリックすると、ShowPopupMessageメソッドが呼び出されます。
トリガーでは、データ入力属性の一部である[Label]、[HideLabel]、[Description]、[HiddenIf]、[DisabledIf]、[Section]、[SectionHiddenIf]をサポートしています。
非同期トリガー
トリガーメソッドは非同期にできます。たとえば、遅延後にメッセージを表示したい場合は、UniTaskを使用できます。
[Trigger]
public async void ShowPopupMessageAfterDelay() { // Note the async keyword
await UniTask.Delay(TimeSpan.FromSeconds(1)); // Wait for 1 second
Context.Service.PromptMessage("Title of the message", "Content of the message");
}
より実用的な例として、処理を続行する前に確認ダイアログを表示できます。
[Trigger]
public async void ShowConfirmationDialog() {
bool confirmed = await Context.Service.PromptConfirmation("Are you sure?", "Do you want to proceed?");
if (confirmed) {
// Proceed
}
}
プログラムからトリガーを呼び出す
データ入力へのアクセスと同様に、メソッドを直接呼び出すか、エンティティのvoid InvokeTrigger(string key)メソッドを使用して、プログラムからトリガーを呼び出せます。たとえば、ShowPopupMessageというトリガーを呼び出すには、entity.ShowPopupMessage()またはentity.InvokeTrigger("ShowPopupMessage")を使用できます。
ポートの順序
既定では、ポートはエンティティクラスでの宣言順に基づいて自動的に並びます。ただし、[DataInput]、[DataOutput]、[FlowInput]、[FlowOutput]、[Trigger]属性のorderパラメーターを使用して、ポートの順序を手動で指定できます。例:
[DataInput(order = 1)]
public int MyNumber = 42;
[DataInput(order = -1)]
public string MyString = "Hello, World!"; // This will be displayed before MyNumber
動的ポート
特定の条件に基づいてポートを動的に追加または削除したい場合があります。たとえば、組み込みのMulti Gateノードには、「Exit Count」データ入力に応じた可変数のフロー出力(出口)があります。
これを実現するには、実行時に基になるポートコレクションへアクセスします。
FlowOutputPortCollection.GetPorts().Clear(); // Clear all flow output ports
for (var i = 1; i <= ExitCount; i++) {
AddFlowOutputPort("Exit" + i, new FlowOutputProperties {
label = "EXIT".Localized() + " " + i
}); // Create a new flow output port for each exit
}
Broadcast(); // Notify the editor that the ports have changed
Multi Gateノードの完全なソースコードはこちらで確認できます。
ポートを頻繁に(つまり毎フレーム)追加または削除しないでください。パフォーマンスの問題を引き起こす可能性があります。
動的ポートプロパティ
ラベル、説明、型固有のプロパティなど、ポートのプロパティも動的に変更できます。たとえば、次のような場合を考えます。
[DataInput]
[IntegerSlider(1, 10)]
public int CurrentItem = 1;
private int itemCount = 10; // Assume we have 10 items initially
[IntegerSlider]の範囲はコンパイル時に決定されます。しかし、itemCountが変更された場合は、スライダーの範囲も更新したくなります。そのためには、ポートプロパティへ直接アクセスします。
var properties = GetDataInputPort(nameof(CurrentItem)).Properties;
properties.description = $"Select from {itemCount} items."; // Change the description
var typeProperties = (IntegerDataInputTypeProperties) properties.typeProperties; // Get type-specific properties
typeProperties.min = 1;
typeProperties.max = itemCount; // Change the slider range
BroadcastDataInputProperties(nameof(CurrentItem)); // Notify the editor that the properties have changed