# NodeScript 系统总览 ## 文件结构 ``` Editor Tools/NodeScript/ ├── NodeCore.cs # 核心类型:生命周期、连接器、动态类型 ├── NodeManager.cs # 调度中心 + UI 交互 ├── NodeObject.cs # MonoBehaviour:节点 UI、插槽、选中、状态显示 ├── ConnectorSlot.cs # 连接点交互 + 外观刷新 ├── NodeUIBuilder.cs # UI 构建:Dropdown / FloatField / Toggle / TypeDropdown ├── NodeCompoments/ │ ├── NodeCompoment.cs # 操作 / 控制流节点 │ └── NodeUtility.cs # 工具 / 常量 / 变量节点 ├── Node重构.txt # 重构需求文档 ├── Node重构大纲.md # 详细重构设计 └── 总览.md # 本文件 ``` --- ## 一、执行模型:拉取式 + 生命周期 ### 1.1 周期调度 ``` RunGraph(): 1. ComputeLValues() — BFS 算所有节点到 Start 的最短距离 L 2. triggerTable = {全部节点} 3. while triggerTable ∪ runtimeTable 非空: RunCycle() RunCycle(): 1. triggerTable 并入 runtimeTable,清空 triggerTable 2. foreach node in runtimeTable: result = node.Loop() 收集 triggers → triggerTable if TriggerDownstream → 自动发现下游节点 → triggerTable if RemoveFromRuntime → 标记移除 3. runtimeTable -= 已完成的节点 ``` ### 1.2 节点状态 | 状态 | 含义 | |---|---| | `Ready` | 等待本周期执行 | | `Hang` | 前置节点未完成,挂起到下一周期 | | `Complete` | 已完成,移出 runtimeTable | ### 1.3 LoopResult 返回类型 | 工厂方法 | RemoveFromRuntime | TriggerDownstream | 用途 | |---|---|---|---| | `Complete()` | true | true | 节点完成,触发下游 | | `Hang(preceding)` | false | false | 等待前置节点 | | `Repeat()` | false | true | 保持活跃+触发下游(循环迭代) | | `Wait()` | false | false | 空转一周期(循环等下游消费) | ### 1.4 L 值 - BFS 从 `NodeStart`/`NodeEntry` 开始计算,L 值显示在节点标题栏 `(L:N)` - 仅显示,不约束连线 - L=-1 表示孤立节点(无路径可达) ### 1.5 防自循环 连线 A→B 时,从 B 出发沿现有边 DFS,看能否回到 A。能回到则拒绝连线。 --- ## 二、类型系统 ### 2.1 连接器体系 ``` IInput IOutput ├── Input (泛型) ├── Output (泛型) └── InputAny (动态) └── OutputAny (动态) ``` ### 2.2 InputAny / OutputAny | 特性 | 说明 | |---|---| | 初始状态 | `DataType = null`,连接点显示**灰色** | | 连线锁定 | 连线后自动 `LockType(T)`,连接点变为类型对应颜色 | | 类型传播 | 锁定后 `OnTypePropagated(T)` → 同节点其他未锁定端口跟随 | | `IsFixedType` | true 时阻止传播覆盖,如 `NodeLerp.t` 固定为 float | | 可改类型 | `LockType` 允许覆盖(防止 `NodeConst` 切换类型后 DataType 不更新) | ### 2.3 Input 拉取模型 - `Value` 优先读 `Output._value`,回退读 `OutputAny` - `ConnectAny(IOutput)` 支持 `OutputAny → Input` 桥接(如 `NodeConst → NodeMath.a`) - `GetSourceNode()` 兼容两种源 ### 2.4 Output 反向写入 - `_writeBack` 回调:`NodeSet` 通过 targetRef 修改 `NodeVariable` 的内部值 ### 2.5 类型兼容表 | from | to | 兼容 | |---|---|---| | null | * | ✓(未锁定端口) | | T | T | ✓ | | int | float | ✓ | | float | int | ✓ | | 其他 | 其他 | ✗ | ### 2.6 类型颜色 | 类型 | 颜色 | |---|---| | float | 蓝 | | int | 青 | | bool | 橙 | | string | 紫 | | Vector2 | 黄 | | Vector3 | 绿 | | Color | 粉 | | GameElement | 深紫 | | Signal | 白 | | List\ | 暗紫 | | null(未锁定) | 灰 | --- ## 三、节点目录 ### 3.1 入口节点 | 节点 | 输入 | 输出 | 说明 | |---|---|---|---| | `NodeStart` | — | `exec(Signal)`, `element(GameElement)` | 图入口,绑定当前选中元素 | | `NodeEntry` | — | `exec(Signal)` | 纯信号入口 | ### 3.2 运算节点 | 节点 | 输入 | 输出 | 说明 | |---|---|---|---| | `NodeMath` | `a(Any)`, `b(Any, fixed)` | `result(Any)` | +-*/ 四则,支持 float/int/V2/V3/Color/string | | `NodeLerp` | `a(Any)`, `b(Any)`, `t(float,fixed)` | `result(Any)` | 线性插值,t 未连默认 0.5 | | `NodeCompare` | `a(Any)`, `b(Any)` | `result(bool)` | == != > < >= <= | ### 3.3 向量节点 | 节点 | 输入 | 输出 | 说明 | |---|---|---|---| | `NodeSplit` | `input(Any)` | `x/y/z/w(float)` | V2 输出 xy,V3 输出 xyz,Color 输出 rgba | | `NodeCombine` | `x/y/z/w(float)` | `output(Any)` | UI 选类型后合并为 V2/V3/Color | | `NodeGetTransform` | `element(GE)` | `pos/rot/scl(V3)` | 读取元素变换 | | `NodeSetTransform` | `exec(Sig)`, `element(GE)`, `Pos/Rot/Scl(V3)` | — | 设置元素变换 | ### 3.4 数据节点 | 节点 | 输入 | 输出 | 说明 | |---|---|---|---| | `NodeConst` | — | `value(Any)` | UI 选类型+填值,6 种类型统一 | | `NodeVariable` | `signal(Sig)`, `set(T)` | `get(T)` | 变量存储;`signal` 连着时等触发才更新 | | `NodeSet` | `targetRef(Any)`, `value(Any)` | — | 反向写入 targetRef 指向的变量 | | `NodeSelect` | `cond(Any)`, `true(Any)`, `false(Any)` | `result(Any)` | 二选一,cond 未连用 UI Toggle | ### 3.5 控制流节点 | 节点 | 输入 | 输出 | 说明 | |---|---|---|---| | `NodeBranch` | `exec(Sig)`, `cond(Any)` | `true/false(Sig)` | cond>0 走 true,否则 false | | `NodeForLoop` | `exec(Sig)`, `count(Any)` | `loopBody(Sig)`, `index(int)`, `completed(Sig)` | 多周期交替:输出→Wait→输出→Wait... | ### 3.6 集合节点 | 节点 | 输入 | 输出 | 说明 | |---|---|---|---| | `NodeList` | — | `output(List)` | 空列表 | | `NodeListAdd` | `list(List)`, `item(T)` | `output(List)` | 追加元素 | | `NodeListGet` | `list(List)`, idx(UI) | `element(T)` | 索引取值 | | `NodeForEach` | `exec(Sig)`, `list(Any)` | `loopBody(Sig)`, `current(Any)`, `index(int)`, `completed(Sig)` | **非泛型**,连 `List` 自动锁定 current 为 X | ### 3.7 GameElement 操作 | 节点 | 输入 | 输出 | 说明 | |---|---|---|---| | `NodeGameElement` | `exec(Sig)`, `Root(GE)`, `Source(GE)` | `newElement(GE)`, `completed(Sig)` | 复制粘贴元素 | | `NodeChildByIndex` | `parent(GE)`, idx(UI) | `child(GE)` | 子元素按索引 | | `NodeChildCount` | `parent(GE)` | `count(int)` | 子元素个数 | | `NodeClone` | `exec(Sig)`, `source(GE)` | `clone(GE)` | Instantiate 克隆 | ### 3.8 调试节点 | 节点 | 输入 | 输出 | 说明 | |---|---|---|---| | `NodeDebugLog` | `exec(Sig)`, `value(Any)` | — | 带 Signal 等待的日志 | | `NodeLog` | `value(Any)` | — | 直接打印日志 | --- ## 四、所有节点统一规范 每个节点的 `Loop()` 遵循: ```csharp public override LoopResult Loop() { // 1. 无输入直接 Complete // 2. EnsureInputsReady() — 所有已连输入的上游必须 Complete // 3. 取值 → 计算 → 设输出 // 4. 返回 Complete / Repeat / Wait } ``` `EnsureInputsReady()` 在 `NodeBase` 上定义,自动检查 `GetPrecedingNodes()` 中所有上游的 Status。 --- ## 五、UI 特性 ### 5.1 节点外观 - 半透明背景 + 标题栏 `Name (L:N)` - `statusImage`:Ready 灰 / Hang 橙黄 / Complete 绿 - 选中节点蓝色高亮 ### 5.2 连接线 - `UILineRenderer` 贝塞尔曲线 - 手动距离检测悬停(不依赖 Unity 射线) - 悬停加粗 +3px,选中加粗 ×2 - RectTransform 自动缩放到包围盒 - 拖线时 dragLine 不参与射线 ### 5.3 交互 | 操作 | 功能 | |---|---| | 左键空白 | 取消所有选中 | | 左键节点 | 选中节点(Shift 多选) | | 左键拖节点 | 移动节点 | | 左键拖输出点→输入点 | 连线 | | 左键线 | 选中线(Shift 多选) | | 中键拖面板 | 平移整个画布 | | Ctrl+右键 | 右键菜单创建节点 | | Delete | 删除选中 | ### 5.4 快捷键 | 快捷键 | 功能 | |---|---| | `F3` | 新建/销毁 NodeScript 编辑器 | | `Enter` | 完整运行图 | | `Shift+Enter` | 单步调试(首次初始化) | | `Esc` | 退出调试模式 | | `Ctrl+Enter` | 运行图(保留) | | `F5` | 拓扑预览(打印分层执行计划) | | `F1` | 保存 | | `F2` | 加载 | | `Ctrl+C/V` | 复制/粘贴节点 | | `Delete` | 删除选中 | ### 5.5 控制台命令 | 命令 | 说明 | |---|---| | `newNode` | 新建/销毁 NodeScript 编辑器 | | `saveNode` | 保存到默认 `graph.json` | | `saveNode name` | 另存为 `{name}.json` | | `loadNode name` | 从 `{name}.json` 加载 | --- ## 六、调试功能 ### 6.1 单步调试 (Shift+Enter) 每步打印详细日志: ``` === Step 3 === ✓ NodeStart (L:0) → Complete ⏳ NodeMath (L:1) → Hang ▶ NodeBranch (L:2) → Ready triggers pending: 2, still running: 3 ``` `statusImage` 同步变色。 ### 6.2 拓扑预览 (F5) ``` ═══ Topological Order (BFS layers) ═══ Layer 0 (2 nodes, 3 downstream wires): Start(L:0), Entry(L:0) Layer 1 (1 nodes, 2 downstream wires): NodeMath(L:1) ... Unreachable (1 nodes): OrphanConst Total: 6 nodes, 5 wires, 3 layers ``` --- ## 七、NodeManager 关键 API | 方法 | 说明 | |---|---| | `Init(GameElement)` | 绑定元素 + 创建 Start 节点 | | `RunGraph()` | 完整执行(生命周期循环) | | `ComputeLValues()` | 重算所有节点的 L 值 + 刷新标题 | | `SaveToFile(string?)` | 保存图,null 用默认路径 | | `LoadFromFile(string?)` | 加载图 | | `GetSavePath(string)` | 获取完整保存路径 | --- ## 八、文件存储 | 项目 | 路径 | |---|---| | 保存目录 | `Assets/StreamingAssets/NodeScript/` | | 默认文件 | `graph.json` | | 自定义文件 | `{name}.json` | --- ## 九、待完成 (Phase 4+) - [ ] Rect 容器(循环 / 子函数体内嵌区域) - [ ] 子控制器(循环体内节点独立调度) - [ ] `NodeSubFunctionDef` / `NodeSubFunctionCall` - [ ] Manager 扫描注册子函数定义