본문으로 건너뛰기

포트 & 트리거

포트와 트리거는 엔티티에 속하며, Warudo 스크립팅에서 가장 중요한 요소라고 할 수 있어요. 이들은 엔티티 간에 데이터를 주고받고, 액션을 트리거하며, 에디터에서 사용자와의 상호작용을 제공해요.

데이터 인풋 포트

데이터 인풋 포트는 사용자가 에디터를 통해 제공하는 데이터를 받거나, 다른 엔티티로부터 데이터를 받을 때 사용돼요. 데이터 인풋은 다양한 타입을 가질 수 있는데, 문자열, 숫자, 불리언 같은 기본적인 타입부터 구조화된 데이터나 배열 같은 복잡한 타입도 지원해요.

데이터 인풋은 [DataInput] 속성으로 표시되며, 엔티티 서브클래스의 public 필드로 정의돼요. 시작하기 예제에서 우리는 숫자 슬라이더를 정의하는 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 };

아래는 에디터에서 어떻게 보이는지의 예시예요 (이 예에서는 노드를 사용하지만, 에셋과 플러그인에서도 동일하게 적용돼요):

코드에서 지정한 기본값으로 데이터 인풋이 초기화되는 방식에 주목해 주세요.이 기본값들은 사용자가 데이터 인풋 레이블 옆에 있는 "Reset" 버튼을 클릭할 때 다시 할당돼요.

데이터 인풋은 일반적으로 직렬화 가능한 타입을 가집니다. 여기서 "직렬화 가능"이란 Warudo를 종료하고 다시 열었을 때 데이터 값이 저장되고 복원될 수 있음을 의미해요. 다음 타입들이 기본적으로 직렬화 가능해요:

  • 기본 타입: int, float, bool, string 그리고 모든 열거형 타입
  • Unity 타입: Vector2, Vector3, Vector4, Color
  • 구조화된 데이터 타입
  • 에셋 레퍼런스
  • 직렬화 가능한 타입의 배열

노드의 경우, 에디터에서 수정할 수는 없지만 노드 자체에서 처리되는 비직렬화 가능한 데이터 인풋도 정의할 수 있어요. 예를 들어, 다음 코드는 제네릭 오브젝트 데이터 인풋에서 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를 반환하면 데이터 인풋이 숨겨지도록 지정해요. 메서드는 엔티티 클래스의 public 또는 protected 메서드여야 하며, bool을 반환해야 해요.
    [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 조건을 만족하면 데이터 인풋이 숨겨지도록 지정해요. value는 상수여야 해요.
    [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를 반환하면 섹션이 숨겨지도록 지정해요. 메서드는 public 또는 protected 메서드여야 하며, bool을 반환해야 해요. @if@is 조건도 지원해요.
  • [Markdown(bool primary = false)]: 데이터 인풋이 Markdown 텍스트로 표시되며 편집할 수 없도록 지정해요. 데이터 인풋은 string 타입이어야 해요. primarytrue면, 더 큰 글씨 크기와 배경색 없이 텍스트가 표시돼요.
정보

[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이어야 해요. 지정된 리소스 유형의 자동완성 리스트로 데이터 인풋을 표시하도록 지정해요. 예를 들어 "Character → Default Idle Animation" 데이터 인풋은 [AutoCompleteResource("CharacterAnimation")]으로 정의돼요. 자세한 내용은 [리소스 프로바이더 및 리졸버] 페이지를 참조하세요.(resource-providers-and-resolvers.md) page for more information.
  • [AutoCompleteList(string methodName, bool forceSelection = false, string defaultLabel = null)]: 데이터 타입이 string 이어야 해요. 지정된 메서드에 의해 생성된 드롭다운 메뉴로 데이터 인풋을 표시하도록 지정해요. 메서드는 엔티티 클래스의 public 또는 protected메서드여야 하며, UniTask<AutoCompleteList>를 반환해야 해요. 메서드는 비동기일 수 있어요. 만약 forceSelectiontrue면 사용자는 드롭다운 리스트에서만 값을 선택할 수 있으며, 선택하지 않으면 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]: 데이터 타입이 열거형이어야 해요. 카드 선택 리스트로 데이터 인풋을 표시하도록 지정해요. (예: "Camera → Control Mode").
    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;
}

에셋이 null이거나 비활성 상태인지 확인하려면 asset.IsNullOrInactive()를 사용하고, 활성 상태인 경우 asset.IsNonNullAndActive()를 사용할 수 있어요..

에셋을 드롭다운 목록에서 필터링하고 싶다면 어떻게 해야 할까요? [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
}

프로그래밍 방식으로 데이터 인풋 액세스

엔티티가 있다고 가정해볼게요. 데이터를 읽는 두 가지 방법이 있어요:

  1. 데이터 인풋 필드에 직접 접근하기. 예를 들어, public int MyNumber = 42;라는 퍼블릭 데이터 인풋 필드를 가진 노드가 있다면 node.MyNumber를 사용해 MyNumber의 값을 직접 읽을 수 있어요.
  2. T GetDataInput<T>(string key) 또는 object GetDataInput(string key) 메서드를 사용하기. 이 메서드는 모든 엔티티에서 사용할 수 있으며, 지정된 이름의 데이터 인풋 값을 반환해요. 예를 들어. MyNumber라는 데이터 인풋을 가진 노드가 있을 때, node.GetDataInput<int>("MyNumber") 또는 스타일적으로 선호되는 node.GetDataInput<int>(nameof(node.MyNumber))를 사용해 MyNumber 값을 읽을 수 있어요.

포트의 키는 필드 이름과 동일하며, 포트가 동적으로 추가된 경우는 제외됩니다(동적 포트 참조).

두 번째 방법은 데이터를 동적으로 접근해야 할 때 유용해요. 예를 들어, 문자열 변수에 따라 데이터를 접근해야 하는 경우에요. (마찬가지로 동적 포트참조.) 그 외에는 두 방법에 큰 차이가 없어요.

마찬가지로, 데이터 인풋에 값을 쓰기 위해서는 데이터 인풋 필드에 직접 값을 할당하거나 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));

하지만 이 경우에는 두 번째 방법을 사용하는 것이 깅력하게 권장되는데, 이유는 두 가지에요:

  1. 데이터 인풋의 변경 사항이 관찰자에게 통지된다는 점.
  2. broadcast 파라미터를 true로 설정하면, 변경 사항이 에디터에 전송돼요. 그렇지 않으면 BroadcastDataInput(string key)를 사용해 수동으로 에디터에 변경 사항을 전송해야 해요.

첫 번째 방법은 다음과 같은 경우에만 사용해야 해요:

  1. 매우 자주 데이터를 업데이트하지만, 매번 에디터에 변경 사항을 전송할 필요가 없을 때. 즉, 가끔 BroadcastDataInput(string key)를 호출해 성능을 절약할 수 있어요.
  2. 데이터 인풋의 관찰자에게 변경 사항을 통지하고 싶지 않을 때. 이는 드문 경우에요.

데이터 아웃풋 포트

데이터 아웃풋 포트는 다른 노드에 데이터를 제공하는 데 사용돼요. 데이터 아웃풋은 노드 하위 클래스에서 [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'이라는 단어로 자동 설정돼요.

트리거

트리거는 에디터에서 클릭해 특정 작업을 실행할 수 있는 버튼이에요. 트리거는 엔티티 하위 클래스에서 [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

Contributions

Edit on Github

Authors

Translators

Last updated on 2024.09.24