Flow Module
UNIVERSAL GRAPH RUNTIME · PROCEDURAL EVERYTHING
Flow
어떤 절차적 표현이든 그래프로.
게임 모드 시퀀스, 텍스쳐 생성, 캐릭터 조립, 이벤트 체인 —
데이터든 로직이든 모든 절차를 노드 그래프로 표현하고 실행하는 범용 런타임입니다.
에디터에서 시각적으로 설계하고, 런타임에서 UniTask 기반 비동기로 실행됩니다.
각 노드는 async — 이벤트 대기, 딜레이, 조건부 분기 모두 자연스럽게 표현
실행 흐름 없이 순수 데이터 변환 — 출력 노드가 평가될 때 필요한 입력을 역방향으로 pull
같은 그래프 구조로 다른 조립 순서나 파츠를 교체 — 조립 로직을 코드 변경 없이 에디터에서 수정
FlowAsset
ScriptableObject그래프 구조를 에셋으로 저장하는 컨테이너. 노드 목록, 실행 링크(ExecutionLink), 값 링크(ValueLink)를 보유합니다.
FlowNode
abstract그래프 내 하나의 실행 단위. 입/출력 포트를 선언하고, 비동기 진입(EnterAsync)과 실행(RunAsync)을 구현합니다.
FlowSession
Runtime그래프를 실제로 실행하는 컨텍스트. EntryNode부터 깊이 우선으로 노드를 순회하며 실행 흐름을 관리합니다.
Blackboard & Properties
Runtime Data세션 내 노드들이 공유하는 데이터 레이어. 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) ); }
[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 ); } }