Flow Module
Flow
Universal Graph Runtime
게임 모드 시퀀스, 텍스쳐 생성, 캐릭터 조립, 이벤트 체인 —
어떤 절차든 노드 그래프로 표현하고 UniTask 기반으로 비동기 실행합니다.
절차적 표현 예시
각 노드는 async — 이벤트 대기, 딜레이, 조건부 분기 모두 자연스럽게 표현
실행 흐름 없이 순수 데이터 변환 — 출력 노드가 평가될 때 입력을 역방향으로 pull
같은 그래프 구조로 조립 순서·파츠 교체 가능 — 코드 변경 없이 에디터에서 수정
핵심 구성요소
그래프 구조를 에셋으로 저장하는 컨테이너. 노드 목록, 실행 링크(ExecutionLink), 값 링크(ValueLink)를 보유합니다.
그래프 내 하나의 실행 단위. 입/출력 포트를 선언하고 비동기 진입(EnterAsync)과 실행(RunAsync)을 구현합니다.
그래프를 실제로 실행하는 컨텍스트. EntryNode부터 깊이 우선으로 순회하며 실행 흐름을 관리합니다.
세션 내 노드들이 공유하는 데이터 레이어. FlowBlackboard는 int 키 딕셔너리, FlowProperty<T>는 Observable 값입니다.
노드 분류
실행 모델
세션 시작 시 각 노드의 입력 포트에 소스 노드를 연결합니다. 이후 실행 중 소스 노드의 출력값이 자동으로 흐릅니다.
pendingNodes 스택에서 노드를 꺼내며 EnterAsync → RunAsync 순으로 실행 후 다음 노드들을 스택에 추가합니다.
노드가 입력값을 필요로 할 때 TryGetInputOverrideData<T>로 연결된 소스에서 값을 당겨옵니다.
모든 async 호출에 동일한 CancellationToken이 전파됩니다. 세션이 End()되거나 오너가 파괴되면 전체 실행이 취소됩니다.
var pending = new List<INode> { flow.EntryNode }; while (pending.Count > 0) { var node = pending[^1]; pending.RemoveAt(^1); ApplyValueInputs(node); // 입력 포트 값 세팅 await node.EnterAsync(ct); // 진입 (딜레이, 준비) await node.RunAsync(ct); // 실행 pending.AddRange( // 다음 노드 추가 flow.GetNextNodes(node) ); }
커스텀 노드 작성
에디터 팔레트에 표시될 이름과 카테고리 지정
슬롯 번호와 값 타입으로 포트 선언
연결된 포트값 없으면 직렬화 필드 fallback
딜레이·대기·비동기 작업을 자연스럽게 표현
[FlowNode("Delay", "Timing")] public sealed class Delay : GameplayFlowNode { // 입력 포트: slot 0, 타입 float private static readonly IReadOnlyList<ValuePortDefinition> _inputs = new[] { new ValuePortDefinition(0, typeof(float)) }; [SerializeField] private float delaySeconds = 1.0f; public override IReadOnlyList<ValuePortDefinition> GetValueInputPorts() => _inputs; public override async UniTask EnterAsync(CancellationToken ct) { var duration = GetInputOverrideOrDefault(0, delaySeconds); if (duration > 0) await UniTask.Delay( TimeSpan.FromSeconds(duration), cancellationToken: ct ); } }