248 lines
10 KiB
Markdown
248 lines
10 KiB
Markdown
|
|
# Ichni NodeScript Web — 独立节点编辑器设计
|
|||
|
|
|
|||
|
|
## 概述
|
|||
|
|
|
|||
|
|
将 Unity 内的 NodeScript 编辑器**整体迁移到 Web**,重新用 Vue 3 + TypeScript 实现。Unity 端精简为只负责执行和执行结果返回,通过 localhost:515 WebSocket 与 Web 前端通信。
|
|||
|
|
|
|||
|
|
引用项目:
|
|||
|
|
- [Blueprint (SVG 节点编辑器)](https://github.com/anan1213095357/Blueprint) — 参考节点分类和交互设计
|
|||
|
|
- [XNode](https://github.com/WPFDevelopersOrg/XNode) — 参考节点编辑器模式
|
|||
|
|
|
|||
|
|
## 架构总览
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
┌──────────────────────────────────┐ WebSocket (JSON-RPC) ┌──────────────────────┐
|
|||
|
|
│ IchniNodeScriptWeb/ │ ◄────────────────────────► │ Unity Editor │
|
|||
|
|
│ │ ws://localhost:515 │ │
|
|||
|
|
│ ┌───────────┐ ┌──────────────┐ │ │ ┌────────────────┐ │
|
|||
|
|
│ │ Vue Flow │ │ NodeEngine │ │ │ │ WS Server 515 │ │
|
|||
|
|
│ │ 编辑器画布 │ │ (TypeScript)│ │ │ ├────────────────┤ │
|
|||
|
|
│ ├───────────┤ ├──────────────┤ │ │ │ ExecutionSvc │ │
|
|||
|
|
│ │ 属性面板 │ │ 纯节点执行 │ │ │ ├────────────────┤ │
|
|||
|
|
│ ├───────────┤ ├──────────────┤ │ │ │ GameElementSvc │ │
|
|||
|
|
│ │ 节点面板 │ │ 图编辑逻辑 │ │ │ └────────────────┘ │
|
|||
|
|
│ ├───────────┤ ├──────────────┤ │ │ │
|
|||
|
|
│ │ WS 通信 │ │ 保存/加载 │ │ │ 删掉了所有 UI 代码 │
|
|||
|
|
│ └───────────┘ └──────────────┘ │ │ │
|
|||
|
|
└──────────────────────────────────┘ └──────────────────────┘
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 技术栈
|
|||
|
|
|
|||
|
|
| 层 | 技术 |
|
|||
|
|
|---|---|
|
|||
|
|
| **构建** | Vite |
|
|||
|
|
| **框架** | Vue 3 (Composition API) |
|
|||
|
|
| **语言** | TypeScript(严格模式) |
|
|||
|
|
| **状态管理** | Pinia |
|
|||
|
|
| **节点编辑器** | Vue Flow (@vue-flow/core) |
|
|||
|
|
| **WebSocket** | 原生 WebSocket API + JSON-RPC 2.0 |
|
|||
|
|
| **Unity WS** | System.Net.WebSockets (内置,无需三方库) |
|
|||
|
|
|
|||
|
|
## 系统架构
|
|||
|
|
|
|||
|
|
### 核心节点系统
|
|||
|
|
|
|||
|
|
后端执行引擎的类型系统(TypeScript):
|
|||
|
|
|
|||
|
|
- **Node** — 节点基类,含 inputs/outputs 连接器、status、loop()
|
|||
|
|
- **Input/Output** — 连接器:name, dataType, color, value
|
|||
|
|
- **LoopResult** — loop() 返回值:Complete / Hang / Repeat / Wait
|
|||
|
|
- **NodeEngine** — 调度器:triggerTable + runtimeTable,参考现有 NodeManager.RunCycle
|
|||
|
|
- **UnityBridgeNode** — 特殊基类,loop() 时将 inputs 打包发送至 Unity,等待返回后填 outputs
|
|||
|
|
|
|||
|
|
### 两种节点类型
|
|||
|
|
|
|||
|
|
| | 纯节点 (PureNode) | Unity 桥接节点 |
|
|||
|
|
|---|---|---|
|
|||
|
|
| 执行位置 | Web 浏览器内 | Unity 进程 |
|
|||
|
|
| 执行方式 | 同步 JS 运算 | WS 请求 → 等待 → 回调 |
|
|||
|
|
| 等待策略 | 立即 Complete | Hang 直到收到回复 |
|
|||
|
|
| 例子 | NodeMath, NodeBranch, NodeForLoop, NodeConst, NodeSplit | NodeGameElement, NodeSetTransform |
|
|||
|
|
|
|||
|
|
### Vue Flow 集成
|
|||
|
|
|
|||
|
|
| Vue Flow 概念 | 对应实现 |
|
|||
|
|
|---|---|
|
|||
|
|
| Node | 自定义 Vue Flow Node,渲染标题栏 + Handle (输入/输出插槽) + 参数 UI |
|
|||
|
|
| Edge | 连线,onConnect 回调中做类型兼容检查 + 环检测 |
|
|||
|
|
| Handle | Input/Output 端口,颜色根据 dataType 变化 |
|
|||
|
|
| Minimap | Vue Flow 内置小地图 |
|
|||
|
|
| Controls | 缩放/平移控件 |
|
|||
|
|
|
|||
|
|
## 组件设计
|
|||
|
|
|
|||
|
|
| 组件 | 职责 |
|
|||
|
|
|---|---|
|
|||
|
|
| **NodeEditor.vue** | 主画布 (Vue Flow)、工具栏、快捷键 |
|
|||
|
|
| **NodePalette.vue** | 节点创建面板(搜索 + 分类,参考 Blueprint 弹窗) |
|
|||
|
|
| **PropertyPanel.vue** | 侧边属性面板(选中节点后显示参数编辑) |
|
|||
|
|
| **StatusBar.vue** | 底部栏:WS 连接状态、执行状态、节点数 |
|
|||
|
|
| **WebSocketService.ts** | WS 连接管理 + JSON-RPC 请求/响应匹配 |
|
|||
|
|
| **editorStore.ts** | Pinia store:图状态、选中、撤销历史 |
|
|||
|
|
|
|||
|
|
## 节点分类
|
|||
|
|
|
|||
|
|
### 纯 Web 节点(~20 个,TypeScript 实现)
|
|||
|
|
|
|||
|
|
| 分类 | 节点 |
|
|||
|
|
|---|---|
|
|||
|
|
| 入口 | NodeStart |
|
|||
|
|
| 数学 | NodeMath (+-*/) |
|
|||
|
|
| 插值 | NodeLerp |
|
|||
|
|
| 比较 | NodeCompare (== != > < >= <=) |
|
|||
|
|
| 向量拆分 | NodeSplit |
|
|||
|
|
| 向量合并 | NodeCombine |
|
|||
|
|
| 常量 | NodeConst (6 种类型统一) |
|
|||
|
|
| 分支 | NodeBranch |
|
|||
|
|
| 循环 | NodeForLoop, NodeForEach |
|
|||
|
|
| 数据 | NodeSelect (二选一), NodeSet (赋值) |
|
|||
|
|
| 变量 | NodeVariable, NodeList, NodeListAdd, NodeListGet |
|
|||
|
|
| 调试 | NodeLog |
|
|||
|
|
|
|||
|
|
### Unity 桥接节点(~5 个)
|
|||
|
|
|
|||
|
|
| 节点 | 功能 | WS 方法 |
|
|||
|
|
|---|---|---|
|
|||
|
|
| NodeGameElement | 复制/粘贴 GameElement | execute_nodes |
|
|||
|
|
| NodeGetTransform | 读取位置/旋转/缩放 | query_gameelement |
|
|||
|
|
| NodeSetTransform | 写入位置/旋转/缩放 | modify_gameelement |
|
|||
|
|
| NodeChildByIndex | 按索引取子元素 | query_gameelement |
|
|||
|
|
| NodeChildCount | 取子元素数量 | query_gameelement |
|
|||
|
|
|
|||
|
|
## WebSocket 协议
|
|||
|
|
|
|||
|
|
### 传输
|
|||
|
|
- 协议:纯 WebSocket (`ws://localhost:515`)
|
|||
|
|
- 格式:JSON 文本帧
|
|||
|
|
- 风格:JSON-RPC 2.0
|
|||
|
|
|
|||
|
|
### 消息格式
|
|||
|
|
|
|||
|
|
**请求 (Web → Unity):**
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"jsonrpc": "2.0",
|
|||
|
|
"id": "req_001",
|
|||
|
|
"method": "execute_nodes",
|
|||
|
|
"params": { ... }
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**响应 (Unity → Web):**
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"jsonrpc": "2.0",
|
|||
|
|
"id": "req_001",
|
|||
|
|
"result": { ... }
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### API 方法
|
|||
|
|
|
|||
|
|
| 方法 | 方向 | 说明 |
|
|||
|
|
|---|---|---|
|
|||
|
|
| `execute_nodes` | Web→Unity | 批量执行 Unity 节点,传入 inputs+params,返回 outputs |
|
|||
|
|
| `query_gameelement` | Web→Unity | 查询 GameElement 属性(transform/children 等) |
|
|||
|
|
| `modify_gameelement` | Web→Unity | 修改 GameElement 属性 |
|
|||
|
|
| `get_type_registry` | Web→Unity | 获取 Unity 端注册的桥接节点类型列表 |
|
|||
|
|
|
|||
|
|
### 连接生命周期
|
|||
|
|
1. Web 打开页面 → 自动 ws://localhost:515 连接
|
|||
|
|
2. 连接成功 → 发送 get_type_registry 获取 Unity 节点类型
|
|||
|
|
3. 连接断开 → Web 显示 "Unity 未连接",禁用运行按钮,可继续编辑
|
|||
|
|
4. 自动重连(每 3 秒尝试)
|
|||
|
|
5. Unity 退出 → Web 仍可编辑
|
|||
|
|
|
|||
|
|
## Unity 端架构
|
|||
|
|
|
|||
|
|
### 删除的代码
|
|||
|
|
- NodeObject.cs (MonoBehaviour 节点 UI)
|
|||
|
|
- ConnectorSlot.cs (连接点交互)
|
|||
|
|
- NodeUIBuilder.cs (UI 控件构建)
|
|||
|
|
- NodeManager 中的 UI 交互:拖线/右键菜单/选中/复制粘贴
|
|||
|
|
- 文件 Save / Load
|
|||
|
|
- 节点列表管理 (allNodes)
|
|||
|
|
|
|||
|
|
### 保留的代码
|
|||
|
|
- NodeCore.cs(核心类型:NodeBase, Input/Output, InputAny/OutputAny, 生命周期)
|
|||
|
|
- NodeCompoment.cs 中部分节点:NodeGameElement, NodeGetTransform, NodeSetTransform, NodeChildByIndex, NodeChildCount
|
|||
|
|
- NodeManager.RunCycle() + RunGraph() — 执行调度
|
|||
|
|
- ComputeLValues — 内部拓扑排序
|
|||
|
|
- 类型检查 / 环检测
|
|||
|
|
|
|||
|
|
### 新增的代码
|
|||
|
|
- NodeWebSocketServer.cs — HttpListener + WebSocket 服务端 (port 515)
|
|||
|
|
- JsonRpcHandler.cs — 消息解包和分发
|
|||
|
|
- NodeExecutionService.cs — 接收图 → 反序列化 → 构建 NodeBase → 执行 → 返回结果
|
|||
|
|
- GameElementQueryService.cs — 查询/修改 GameElement 属性
|
|||
|
|
|
|||
|
|
### 执行流程(Web 发图 → Unity 执行)
|
|||
|
|
1. Web 用户点击"运行"
|
|||
|
|
2. Web 收集所有 Unity 节点的 inputs + params
|
|||
|
|
3. WS 发送 `execute_nodes` { nodes: [...] }
|
|||
|
|
4. Unity 接收 → 反序列化 GraphData → 重建 NodeBase → 设置连接 → RunGraph()
|
|||
|
|
5. 收集每个节点的 outputs → WS 返回结果
|
|||
|
|
6. Web 填入对应节点 outputs → 继续执行剩余纯节点
|
|||
|
|
|
|||
|
|
## 序列化
|
|||
|
|
|
|||
|
|
JSON 文件格式,保存在 `IchniNodeScriptWeb/saves/` 下:
|
|||
|
|
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"version": "1.0",
|
|||
|
|
"entryNode": "start",
|
|||
|
|
"nodes": [
|
|||
|
|
{ "id": "n_1", "type": "NodeStart", "position": { "x": -400, "y": 0 }, "params": {} }
|
|||
|
|
],
|
|||
|
|
"edges": [
|
|||
|
|
{ "from": "n_1", "fromPort": "exec", "to": "n_2", "toPort": "exec" }
|
|||
|
|
]
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
每个节点 params 包含节点特定参数(类型选择、数值等),端口连接信息在 edges 数组中。
|
|||
|
|
|
|||
|
|
## 项目文件结构
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
IchniNodeScriptWeb/
|
|||
|
|
├── package.json
|
|||
|
|
├── vite.config.ts
|
|||
|
|
├── tsconfig.json
|
|||
|
|
├── index.html
|
|||
|
|
├── src/
|
|||
|
|
│ ├── main.ts
|
|||
|
|
│ ├── App.vue
|
|||
|
|
│ ├── components/
|
|||
|
|
│ │ ├── NodeEditor.vue # Vue Flow 主画布
|
|||
|
|
│ │ ├── NodePalette.vue # 节点创建面板
|
|||
|
|
│ │ ├── PropertyPanel.vue # 属性编辑面板
|
|||
|
|
│ │ └── StatusBar.vue # 底部状态栏
|
|||
|
|
│ ├── nodes/
|
|||
|
|
│ │ ├── engine/
|
|||
|
|
│ │ │ ├── types.ts # 核心类型定义
|
|||
|
|
│ │ │ ├── NodeEngine.ts # 执行引擎
|
|||
|
|
│ │ │ └── UnityBridgeNode.ts # 桥接节点基类
|
|||
|
|
│ │ ├── math/MathNode.ts
|
|||
|
|
│ │ ├── control/BranchNode.ts, ForLoopNode.ts, ForEachNode.ts
|
|||
|
|
│ │ ├── data/ConstNode.ts, VariableNode.ts, SelectNode.ts, SetNode.ts
|
|||
|
|
│ │ ├── vector/SplitNode.ts, CombineNode.ts
|
|||
|
|
│ │ ├── logic/CompareNode.ts, LerpNode.ts
|
|||
|
|
│ │ ├── debug/LogNode.ts
|
|||
|
|
│ │ └── unity/
|
|||
|
|
│ │ ├── GameElementNode.ts
|
|||
|
|
│ │ ├── GetTransformNode.ts
|
|||
|
|
│ │ ├── SetTransformNode.ts
|
|||
|
|
│ │ ├── ChildByIndexNode.ts
|
|||
|
|
│ │ └── ChildCountNode.ts
|
|||
|
|
│ ├── services/
|
|||
|
|
│ │ ├── WebSocketService.ts # WS 连接 + JSON-RPC
|
|||
|
|
│ │ └── GraphSerializer.ts # 保存/加载 JSON
|
|||
|
|
│ └── stores/
|
|||
|
|
│ └── editorStore.ts # Pinia 状态
|
|||
|
|
├── saves/ # 图的 JSON 文件
|
|||
|
|
└── public/
|
|||
|
|
```
|