330 lines
9.9 KiB
Markdown
330 lines
9.9 KiB
Markdown
|
|
# 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<T> (泛型) ├── Output<T> (泛型)
|
|||
|
|
└── InputAny (动态) └── OutputAny (动态)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 2.2 InputAny / OutputAny
|
|||
|
|
|
|||
|
|
| 特性 | 说明 |
|
|||
|
|
|---|---|
|
|||
|
|
| 初始状态 | `DataType = null`,连接点显示**灰色** |
|
|||
|
|
| 连线锁定 | 连线后自动 `LockType(T)`,连接点变为类型对应颜色 |
|
|||
|
|
| 类型传播 | 锁定后 `OnTypePropagated(T)` → 同节点其他未锁定端口跟随 |
|
|||
|
|
| `IsFixedType` | true 时阻止传播覆盖,如 `NodeLerp.t` 固定为 float |
|
|||
|
|
| 可改类型 | `LockType` 允许覆盖(防止 `NodeConst` 切换类型后 DataType 不更新) |
|
|||
|
|
|
|||
|
|
### 2.3 Input<T> 拉取模型
|
|||
|
|
|
|||
|
|
- `Value` 优先读 `Output<T>._value`,回退读 `OutputAny`
|
|||
|
|
- `ConnectAny(IOutput)` 支持 `OutputAny → Input<T>` 桥接(如 `NodeConst → NodeMath.a`)
|
|||
|
|
- `GetSourceNode()` 兼容两种源
|
|||
|
|
|
|||
|
|
### 2.4 Output<T> 反向写入
|
|||
|
|
|
|||
|
|
- `_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\<GameElement\> | 暗紫 |
|
|||
|
|
| 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<T>` | `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<T>` | — | `output(List<T>)` | 空列表 |
|
|||
|
|
| `NodeListAdd<T>` | `list(List<T>)`, `item(T)` | `output(List<T>)` | 追加元素 |
|
|||
|
|
| `NodeListGet<T>` | `list(List<T>)`, idx(UI) | `element(T)` | 索引取值 |
|
|||
|
|
| `NodeForEach` | `exec(Sig)`, `list(Any)` | `loopBody(Sig)`, `current(Any)`, `index(int)`, `completed(Sig)` | **非泛型**,连 `List<X>` 自动锁定 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 扫描注册子函数定义
|