feat: 修复节点变换属性竞态问题,扩展完整属性支持,增强获取数据工具
- fix: 将 update-node-transform 中 scaleX/scaleY 从异步 IPC 改为直接赋值,修复设置不生效 - fix: 将 color 从异步 IPC 改为直接赋值 (node.color = new cc.Color().fromHEX(color)) - feat: update_node_transform 新增 rotation/anchorX/anchorY/opacity/skewX/skewY 参数 - feat: get_scene_hierarchy includeDetails 新增 rotation/anchor/color/opacity/skew/group 返回 - feat: callSceneScriptWithTimeout 对 panel not found 错误返回友好中文提示 - docs: 更新 UPDATE_LOG.md/注意事项.md/project-rules.md 反映所有修改
This commit is contained in:
@@ -7,18 +7,20 @@ description: "MCP Bridge 插件开发规则和编码规范"
|
|||||||
|
|
||||||
## 1. 语言规范 (Language)
|
## 1. 语言规范 (Language)
|
||||||
|
|
||||||
* **强制中文**: 所有的对话回复、代码注释、以及生成的文档都必须使用**中文**。
|
- **强制中文**: 所有的对话回复、代码注释、以及生成的文档都必须使用**中文**。
|
||||||
* **日志消息**: `addLog()` 的消息内容可以使用中英文混合,确保关键术语清晰。
|
- **日志消息**: `addLog()` 的消息内容可以使用中英文混合,确保关键术语清晰。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 2. 关键工作流程 (Critical Workflow)
|
## 2. 关键工作流程 (Critical Workflow)
|
||||||
|
|
||||||
### 2.1 插件重载
|
### 2.1 插件重载
|
||||||
|
|
||||||
- **必须重载**: 修改 `main.js`, `package.json`, `scene-script.js`, 或 `panel/` 后,**必须**在编辑器中执行「扩展 → 刷新」或重启编辑器。
|
- **必须重载**: 修改 `main.js`, `package.json`, `scene-script.js`, 或 `panel/` 后,**必须**在编辑器中执行「扩展 → 刷新」或重启编辑器。
|
||||||
- **热更新不适用**: Cocos Creator 2.x 的插件主进程脚本不支持热更新。
|
- **热更新不适用**: Cocos Creator 2.x 的插件主进程脚本不支持热更新。
|
||||||
|
|
||||||
### 2.2 测试驱动
|
### 2.2 测试驱动
|
||||||
|
|
||||||
- **测试脚本**: 每个新功能必须在 `test/` 目录下创建独立测试脚本 (如 `test/test_feature.js`)。
|
- **测试脚本**: 每个新功能必须在 `test/` 目录下创建独立测试脚本 (如 `test/test_feature.js`)。
|
||||||
- **HTTP 验证**: 测试脚本通过 HTTP 请求直接调用插件 API,验证功能正确性。
|
- **HTTP 验证**: 测试脚本通过 HTTP 请求直接调用插件 API,验证功能正确性。
|
||||||
- **运行前提**: 确保 Cocos Creator 编辑器已打开且 MCP Bridge 服务已启动。
|
- **运行前提**: 确保 Cocos Creator 编辑器已打开且 MCP Bridge 服务已启动。
|
||||||
@@ -29,10 +31,10 @@ description: "MCP Bridge 插件开发规则和编码规范"
|
|||||||
|
|
||||||
### 3.1 进程职责划分
|
### 3.1 进程职责划分
|
||||||
|
|
||||||
| 文件 | 进程 | 可访问 | 不可访问 |
|
| 文件 | 进程 | 可访问 | 不可访问 |
|
||||||
|------|------|--------|----------|
|
| ----------------- | ------------------- | ------------------------------------------- | ------------------------------------- |
|
||||||
| `main.js` | 主进程 (Main) | `Editor.assetdb`, `Editor.Ipc`, `require()` | `cc.*` (Cocos 引擎) |
|
| `main.js` | 主进程 (Main) | `Editor.assetdb`, `Editor.Ipc`, `require()` | `cc.*` (Cocos 引擎) |
|
||||||
| `scene-script.js` | 渲染进程 (Renderer) | `cc.*`, `cc.engine`, `cc.director` | `Editor.assetdb`, `Editor.FileSystem` |
|
| `scene-script.js` | 渲染进程 (Renderer) | `cc.*`, `cc.engine`, `cc.director` | `Editor.assetdb`, `Editor.FileSystem` |
|
||||||
|
|
||||||
### 3.2 跨进程通信规则
|
### 3.2 跨进程通信规则
|
||||||
|
|
||||||
@@ -59,14 +61,14 @@ description: "MCP Bridge 插件开发规则和编码规范"
|
|||||||
|
|
||||||
### 4.1 命名规范
|
### 4.1 命名规范
|
||||||
|
|
||||||
| 类型 | 规范 | 示例 |
|
| 类型 | 规范 | 示例 |
|
||||||
|------|------|------|
|
| ---------- | -------------------- | --------------------------------- |
|
||||||
| 函数名 | camelCase | `handleMcpCall`, `manageScript` |
|
| 函数名 | camelCase | `handleMcpCall`, `manageScript` |
|
||||||
| 常量 | SCREAMING_SNAKE_CASE | `MAX_RESULTS`, `DEFAULT_PORT` |
|
| 常量 | SCREAMING_SNAKE_CASE | `MAX_RESULTS`, `DEFAULT_PORT` |
|
||||||
| 私有变量 | _camelCase | `_isMoving`, `_timer` |
|
| 私有变量 | \_camelCase | `_isMoving`, `_timer` |
|
||||||
| 布尔变量 | is/has/can 前缀 | `isSceneBusy`, `hasComponent` |
|
| 布尔变量 | is/has/can 前缀 | `isSceneBusy`, `hasComponent` |
|
||||||
| MCP 工具名 | snake_case | `get_selected_node`, `manage_vfx` |
|
| MCP 工具名 | snake_case | `get_selected_node`, `manage_vfx` |
|
||||||
| IPC 消息名 | kebab-case | `get-hierarchy`, `create-node` |
|
| IPC 消息名 | kebab-case | `get-hierarchy`, `create-node` |
|
||||||
|
|
||||||
### 4.2 函数组织顺序
|
### 4.2 函数组织顺序
|
||||||
|
|
||||||
@@ -78,26 +80,26 @@ module.exports = {
|
|||||||
"scene-script": "scene-script.js",
|
"scene-script": "scene-script.js",
|
||||||
|
|
||||||
// 2. 生命周期函数
|
// 2. 生命周期函数
|
||||||
load() { },
|
load() {},
|
||||||
unload() { },
|
unload() {},
|
||||||
|
|
||||||
// 3. 服务器管理
|
// 3. 服务器管理
|
||||||
startServer(port) { },
|
startServer(port) {},
|
||||||
stopServer() { },
|
stopServer() {},
|
||||||
|
|
||||||
// 4. 核心处理逻辑
|
// 4. 核心处理逻辑
|
||||||
handleMcpCall(name, args, callback) { },
|
handleMcpCall(name, args, callback) {},
|
||||||
|
|
||||||
// 5. 工具函数 (按字母顺序)
|
// 5. 工具函数 (按字母顺序)
|
||||||
applyTextEdits(args, callback) { },
|
applyTextEdits(args, callback) {},
|
||||||
batchExecute(args, callback) { },
|
batchExecute(args, callback) {},
|
||||||
// ...
|
// ...
|
||||||
|
|
||||||
// 6. IPC 消息处理
|
// 6. IPC 消息处理
|
||||||
messages: {
|
messages: {
|
||||||
"open-test-panel"() { },
|
"open-test-panel"() {},
|
||||||
// ...
|
// ...
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -106,6 +108,7 @@ module.exports = {
|
|||||||
> ⚠️ **重要**: `main.js` 已存在重复函数问题,编辑前务必使用 `view_file` 确认上下文,避免创建重复定义。
|
> ⚠️ **重要**: `main.js` 已存在重复函数问题,编辑前务必使用 `view_file` 确认上下文,避免创建重复定义。
|
||||||
|
|
||||||
**检查清单**:
|
**检查清单**:
|
||||||
|
|
||||||
- [ ] 新增函数前,搜索是否已存在同名函数
|
- [ ] 新增函数前,搜索是否已存在同名函数
|
||||||
- [ ] 修改函数时,确认只有一个定义
|
- [ ] 修改函数时,确认只有一个定义
|
||||||
- [ ] `messages` 对象中避免重复的消息处理器
|
- [ ] `messages` 对象中避免重复的消息处理器
|
||||||
@@ -122,16 +125,16 @@ addLog("mcp", `REQ -> [${toolName}]`);
|
|||||||
addLog("success", `RES <- [${toolName}] 成功`);
|
addLog("success", `RES <- [${toolName}] 成功`);
|
||||||
|
|
||||||
// ❌ 错误
|
// ❌ 错误
|
||||||
console.log("服务启动成功"); // 不会被 read_console 捕获
|
console.log("服务启动成功"); // 不会被 read_console 捕获
|
||||||
```
|
```
|
||||||
|
|
||||||
| type | 用途 | 颜色 |
|
| type | 用途 | 颜色 |
|
||||||
|------|------|------|
|
| --------- | ------------- | ---- |
|
||||||
| `info` | 一般信息 | 蓝色 |
|
| `info` | 一般信息 | 蓝色 |
|
||||||
| `success` | 操作成功 | 绿色 |
|
| `success` | 操作成功 | 绿色 |
|
||||||
| `warn` | 警告信息 | 黄色 |
|
| `warn` | 警告信息 | 黄色 |
|
||||||
| `error` | 错误信息 | 红色 |
|
| `error` | 错误信息 | 红色 |
|
||||||
| `mcp` | MCP 请求/响应 | 紫色 |
|
| `mcp` | MCP 请求/响应 | 紫色 |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -148,13 +151,15 @@ Editor.Ipc.sendToPanel("scene", "scene:set-property", {
|
|||||||
path: "x",
|
path: "x",
|
||||||
type: "Float",
|
type: "Float",
|
||||||
value: 100,
|
value: 100,
|
||||||
isSubProp: false
|
isSubProp: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
// ❌ 不支持 Undo (直接修改)
|
// ⚠️ 不支持 Undo,但同步生效(update-node-transform 使用此方式)
|
||||||
node.x = 100;
|
node.x = 100;
|
||||||
```
|
```
|
||||||
|
|
||||||
|
> **注意**: `update-node-transform` 中所有 13 个属性均使用直接赋值方式,这是为了解决异步 IPC 竞态条件导致属性不生效的问题。此为设计性 trade-off:牺牲 Undo 支持以保证属性即时可靠生效。
|
||||||
|
|
||||||
### 5.2 使用 Undo 组
|
### 5.2 使用 Undo 组
|
||||||
|
|
||||||
对于复合操作,使用 Undo 组包装:
|
对于复合操作,使用 Undo 组包装:
|
||||||
@@ -193,7 +198,7 @@ const defaultPaths = [
|
|||||||
"db://internal/image/default_sprite_splash",
|
"db://internal/image/default_sprite_splash",
|
||||||
"db://internal/image/default_sprite_splash.png",
|
"db://internal/image/default_sprite_splash.png",
|
||||||
"db://internal/image/default_particle",
|
"db://internal/image/default_particle",
|
||||||
"db://internal/image/default_particle.png"
|
"db://internal/image/default_particle.png",
|
||||||
];
|
];
|
||||||
|
|
||||||
for (const path of defaultPaths) {
|
for (const path of defaultPaths) {
|
||||||
@@ -226,12 +231,12 @@ setTimeout(() => {
|
|||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
// ✅ 标准风格
|
// ✅ 标准风格
|
||||||
callback(null, result); // 成功
|
callback(null, result); // 成功
|
||||||
callback("Error message"); // 失败 (字符串)
|
callback("Error message"); // 失败 (字符串)
|
||||||
callback(new Error("message")); // 失败 (Error 对象)
|
callback(new Error("message")); // 失败 (Error 对象)
|
||||||
|
|
||||||
// 避免混用
|
// 避免混用
|
||||||
callback(err, null); // 不推荐,保持一致性
|
callback(err, null); // 不推荐,保持一致性
|
||||||
```
|
```
|
||||||
|
|
||||||
### 7.2 异步操作错误处理
|
### 7.2 异步操作错误处理
|
||||||
@@ -252,22 +257,22 @@ Editor.assetdb.queryInfoByUrl(path, (err, info) => {
|
|||||||
|
|
||||||
使用 [Conventional Commits](https://conventionalcommits.org/) 格式:
|
使用 [Conventional Commits](https://conventionalcommits.org/) 格式:
|
||||||
|
|
||||||
| 类型 | 用途 | 示例 |
|
| 类型 | 用途 | 示例 |
|
||||||
|------|------|------|
|
| ---------- | -------- | -------------------------------------------- |
|
||||||
| `feat` | 新功能 | `feat: add manage_vfx tool` |
|
| `feat` | 新功能 | `feat: add manage_vfx tool` |
|
||||||
| `fix` | 修复 bug | `fix: resolve duplicate function in main.js` |
|
| `fix` | 修复 bug | `fix: resolve duplicate function in main.js` |
|
||||||
| `docs` | 文档更新 | `docs: add code review report` |
|
| `docs` | 文档更新 | `docs: add code review report` |
|
||||||
| `refactor` | 重构 | `refactor: split main.js into modules` |
|
| `refactor` | 重构 | `refactor: split main.js into modules` |
|
||||||
| `test` | 测试 | `test: add material management tests` |
|
| `test` | 测试 | `test: add material management tests` |
|
||||||
| `chore` | 杂项 | `chore: update dependencies` |
|
| `chore` | 杂项 | `chore: update dependencies` |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 9. 已知问题 (Known Issues)
|
## 9. 已知问题 (Known Issues)
|
||||||
|
|
||||||
| 问题 | 原因 | 解决方案 |
|
| 问题 | 原因 | 解决方案 |
|
||||||
|------|------|----------|
|
| ---------------------------------------- | ------------------------------------- | ---------------------------------- |
|
||||||
| "Unknown object to record" 错误 | Cocos 2.4.x Undo 系统与 MCP 交互问题 | 可忽略,不影响功能 |
|
| "Unknown object to record" 错误 | Cocos 2.4.x Undo 系统与 MCP 交互问题 | 可忽略,不影响功能 |
|
||||||
| "sendToMain scene:stash-and-save failed" | 时序问题 | 手动 Ctrl+S 保存 |
|
| "sendToMain scene:stash-and-save failed" | 时序问题 | 手动 Ctrl+S 保存 |
|
||||||
| 颜色修改不支持 Undo | 使用 scene-script 直接修改 | 待优化 |
|
| `update-node-transform` 不支持 Undo | 为解决异步 IPC 竞态问题,改用直接赋值 | 设计性 trade-off,保证属性即时生效 |
|
||||||
| execute_menu_item 仅支持部分菜单 | 缺乏通用菜单 IPC | 添加菜单映射表 |
|
| execute_menu_item 仅支持部分菜单 | 缺乏通用菜单 IPC | 添加菜单映射表 |
|
||||||
|
|||||||
@@ -347,5 +347,40 @@
|
|||||||
|
|
||||||
### 3. Texture2D -> SpriteFrame 子资源 UUID 自动解析
|
### 3. Texture2D -> SpriteFrame 子资源 UUID 自动解析
|
||||||
|
|
||||||
- **问题**: AI 大模型在查找图片引用时,通常只知道 Texture2D (原图) 的 UUID,而 `cc.Sprite.spriteFrame` 实际引用的是其子资源 SpriteFrame 的 UUID,导致查找结果为空。
|
- **问题**: AI 传入图片 (Texture2D) 的 UUID 时,`cc.Sprite.spriteFrame` 实际引用的是该图片的子资源 SpriteFrame(具有不同的 UUID),导致直接查找返回空结果。
|
||||||
- **修复**: 在 `src/main.js` 的 `find_references` 路由中,调用 scene-script 前自动读取目标 UUID 对应资源的 `.meta` 文件,提取所有 `subMetas` 中的子资源 UUID (如 SpriteFrame),作为 `additionalIds` 传递给 scene-script。scene-script 将这些额外 UUID 及其压缩/解压变体一并加入匹配列表,实现 "传入 Texture2D UUID 也能查到 SpriteFrame 引用" 的透明体验。
|
- **修复**: 在 `src/main.js` 的 `find_references` 路由中,调用 scene-script 前自动读取目标 UUID 对应资源的 `.meta` 文件,提取所有 `subMetas` 中的子资源 UUID (如 SpriteFrame),作为 `additionalIds` 传递给 scene-script。scene-script 将这些额外 UUID 及其压缩/解压变体一并加入匹配列表,实现 "传入 Texture2D UUID 也能查到 SpriteFrame 引用" 的透明体验。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 节点变换属性修复与数据增强 (2026-03-01)
|
||||||
|
|
||||||
|
### 1. `update-node-transform` 属性设置方式修复 (`src/scene-script.js`)
|
||||||
|
|
||||||
|
- **问题**: `update-node-transform` 中 `x`, `y`, `width`, `height`, `scaleX`, `scaleY` 六个属性通过异步 IPC `Editor.Ipc.sendToPanel("scene", "scene:set-property", ...)` 设置,存在 fire-and-forget 问题,函数在属性实际生效前就已返回成功回复。导致宽高不生效(Sprite 的 sizeMode 可能在异步消息到达前重置)、坐标不生效(异步 IPC 竞态条件)、批量操作属性丢失等问题。
|
||||||
|
- **修复**: 将所有属性设置统一改为在 scene-script(渲染进程)中直接对节点属性同步赋值(如 `node.x = Number(x)`),与 `set-property` 和 `create-node` 中的处理方式保持一致。`color` 同步改为直接赋值 `node.color = new cc.Color().fromHEX(color)`。
|
||||||
|
- **已验证**: 全部 7 个属性(x, y, width, height, scaleX, scaleY, color)批量设置后均即时生效。
|
||||||
|
|
||||||
|
### 2. `update_node_transform` 工具参数扩展 (`src/main.js` + `src/scene-script.js`)
|
||||||
|
|
||||||
|
- **问题**: 编辑器属性面板中的 Rotation、Anchor、Opacity、Skew 四类属性无法通过 `update_node_transform` MCP 工具设置和获取。
|
||||||
|
- **修复**:
|
||||||
|
- 在 `src/main.js` 的工具 `inputSchema` 中新增 `rotation`, `anchorX`, `anchorY`, `opacity`, `skewX`, `skewY` 六个参数定义。
|
||||||
|
- 在 `src/scene-script.js` 的 `update-node-transform` 处理函数中新增对应的直接赋值逻辑(`node.angle`, `node.anchorX`, `node.anchorY`, `node.opacity`, `node.skewX`, `node.skewY`)。
|
||||||
|
- **已验证**: 全部 13 个属性(x, y, rotation, width, height, scaleX, scaleY, anchorX, anchorY, color, opacity, skewX, skewY)批量设置后均即时生效。
|
||||||
|
|
||||||
|
### 3. `get_scene_hierarchy` 节点详情数据增强 (`src/scene-script.js`)
|
||||||
|
|
||||||
|
- **问题**: `includeDetails` 模式仅返回 position, scale, size 三类数据,缺少 rotation, anchor, color, opacity, skew, group 信息,无法通过 API 完整验证节点属性。
|
||||||
|
- **修复**: 在 `dumpNodes` 函数的 `includeDetails` 分支中新增六个返回字段:
|
||||||
|
- `rotation`: `node.angle`
|
||||||
|
- `anchor`: `{ x: node.anchorX, y: node.anchorY }`
|
||||||
|
- `color`: `{ r: node.color.r, g: node.color.g, b: node.color.b }`
|
||||||
|
- `opacity`: `node.opacity`
|
||||||
|
- `skew`: `{ x: node.skewX, y: node.skewY }`
|
||||||
|
- `group`: `node.group`
|
||||||
|
- **效果**: `includeDetails` 现在返回与编辑器属性面板完全一致的所有节点属性。
|
||||||
|
|
||||||
|
### 4. Scene 面板未就绪友好提示 (`src/main.js`)
|
||||||
|
|
||||||
|
- **问题**: 插件重载或场景切换期间调用 scene-script 方法时,原始错误 `Error: ipc failed to send, panel not found` 信息晦涩,容易让用户误以为插件出现严重故障。
|
||||||
|
- **修复**: 在 `callSceneScriptWithTimeout` 的回调中检测 `panel not found` 错误,自动替换为友好中文提示:`场景面板尚未就绪(可能正在重载插件或切换场景),请等待几秒后重试`。日志级别从 `error` 降为 `warn`。
|
||||||
|
|||||||
@@ -114,8 +114,9 @@
|
|||||||
|
|
||||||
### 8.2 属性修改方式
|
### 8.2 属性修改方式
|
||||||
|
|
||||||
- **核心规则**:在 `scene-script.js` 中严禁直接使用 `node.x = 100`。
|
- **`update-node-transform` 的做法**:出于解决异步 IPC 竞态条件的考量,`update-node-transform` 中所有 13 个属性(x, y, rotation, width, height, scaleX, scaleY, anchorX, anchorY, color, opacity, skewX, skewY)均使用**直接赋值**(如 `node.x = 100`)。直接赋值是同步操作,确保属性在函数返回前已生效,但**不支持 Undo**。
|
||||||
- **正确做法**:必须通过 `Editor.Ipc.sendToPanel('scene', 'scene:set-property', ...)`。只有这样,修改才会被 Cocos Creator 的 UndoManager 捕获,从而支持撤销。
|
- **需要 Undo 的场景**:如果业务要求支持撤销,应改用 `Editor.Ipc.sendToPanel('scene', 'scene:set-property', ...)`,但需注意异步竞态风险。
|
||||||
|
- **`get_scene_hierarchy` 的 `includeDetails`**:现在返回与编辑器属性面板完全一致的节点数据,包括 position, rotation, scale, anchor, size, color, opacity, skew, group。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
19
src/main.js
19
src/main.js
@@ -89,7 +89,18 @@ function callSceneScriptWithTimeout(pluginName, method, args, callback, timeout
|
|||||||
if (!settled) {
|
if (!settled) {
|
||||||
settled = true;
|
settled = true;
|
||||||
clearTimeout(timer);
|
clearTimeout(timer);
|
||||||
callback(err, result);
|
// 友好化处理 Scene 面板未就绪的错误(如插件重载、场景切换期间)
|
||||||
|
if (err && typeof err === "object" && err.message && err.message.includes("panel not found")) {
|
||||||
|
const friendlyMsg = `场景面板尚未就绪(可能正在重载插件或切换场景),请等待几秒后重试。原始信息: ${err.message}`;
|
||||||
|
addLog("warn", `[scene-script] ${friendlyMsg}`);
|
||||||
|
callback(friendlyMsg);
|
||||||
|
} else if (err && typeof err === "string" && err.includes("panel not found")) {
|
||||||
|
const friendlyMsg = `场景面板尚未就绪(可能正在重载插件或切换场景),请等待几秒后重试。原始信息: ${err}`;
|
||||||
|
addLog("warn", `[scene-script] ${friendlyMsg}`);
|
||||||
|
callback(friendlyMsg);
|
||||||
|
} else {
|
||||||
|
callback(err, result);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -286,11 +297,17 @@ const getToolsList = () => {
|
|||||||
id: { type: "string", description: "节点 UUID" },
|
id: { type: "string", description: "节点 UUID" },
|
||||||
x: { type: "number" },
|
x: { type: "number" },
|
||||||
y: { type: "number" },
|
y: { type: "number" },
|
||||||
|
rotation: { type: "number", description: "旋转角度" },
|
||||||
width: { type: "number" },
|
width: { type: "number" },
|
||||||
height: { type: "number" },
|
height: { type: "number" },
|
||||||
scaleX: { type: "number" },
|
scaleX: { type: "number" },
|
||||||
scaleY: { type: "number" },
|
scaleY: { type: "number" },
|
||||||
|
anchorX: { type: "number", description: "锚点 X (0~1)" },
|
||||||
|
anchorY: { type: "number", description: "锚点 Y (0~1)" },
|
||||||
color: { type: "string", description: "HEX 颜色代码如 #FF0000" },
|
color: { type: "string", description: "HEX 颜色代码如 #FF0000" },
|
||||||
|
opacity: { type: "number", description: "透明度 (0~255)" },
|
||||||
|
skewX: { type: "number", description: "倾斜 X" },
|
||||||
|
skewY: { type: "number", description: "倾斜 Y" },
|
||||||
},
|
},
|
||||||
required: ["id"],
|
required: ["id"],
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -107,8 +107,14 @@ module.exports = {
|
|||||||
if (includeDetails) {
|
if (includeDetails) {
|
||||||
nodeData.active = node.active;
|
nodeData.active = node.active;
|
||||||
nodeData.position = { x: Math.round(node.x), y: Math.round(node.y) };
|
nodeData.position = { x: Math.round(node.x), y: Math.round(node.y) };
|
||||||
|
nodeData.rotation = node.angle;
|
||||||
nodeData.scale = { x: node.scaleX, y: node.scaleY };
|
nodeData.scale = { x: node.scaleX, y: node.scaleY };
|
||||||
|
nodeData.anchor = { x: node.anchorX, y: node.anchorY };
|
||||||
nodeData.size = { width: node.width, height: node.height };
|
nodeData.size = { width: node.width, height: node.height };
|
||||||
|
nodeData.color = { r: node.color.r, g: node.color.g, b: node.color.b };
|
||||||
|
nodeData.opacity = node.opacity;
|
||||||
|
nodeData.skew = { x: node.skewX, y: node.skewY };
|
||||||
|
nodeData.group = node.group;
|
||||||
nodeData.components = comps.map((c) => cc.js.getClassName(c));
|
nodeData.components = comps.map((c) => cc.js.getClassName(c));
|
||||||
} else {
|
} else {
|
||||||
// 简略模式下如果存在组件,至少提供一个极简列表让 AI 知道节点的作用
|
// 简略模式下如果存在组件,至少提供一个极简列表让 AI 知道节点的作用
|
||||||
@@ -137,9 +143,9 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 批量更新节点的变换信息 (坐标、缩放、颜色)
|
* 批量更新节点的变换信息 (坐标、缩放、颜色等)
|
||||||
* @param {Object} event IPC 事件对象
|
* @param {Object} event IPC 事件对象
|
||||||
* @param {Object} args 参数 (id, x, y, scaleX, scaleY, color)
|
* @param {Object} args 参数 (id, x, y, rotation, scaleX, scaleY, anchorX, anchorY, color, opacity, skewX, skewY, width, height)
|
||||||
*/
|
*/
|
||||||
"update-node-transform": function (event, args) {
|
"update-node-transform": function (event, args) {
|
||||||
const { id, x, y, scaleX, scaleY, color } = args;
|
const { id, x, y, scaleX, scaleY, color } = args;
|
||||||
@@ -147,64 +153,45 @@ module.exports = {
|
|||||||
let node = findNode(id);
|
let node = findNode(id);
|
||||||
|
|
||||||
if (node) {
|
if (node) {
|
||||||
// 使用 scene:set-property 实现支持 Undo 的属性修改
|
// 直接赋值,确保同步生效
|
||||||
// 注意:IPC 消息需要发送到 'scene' 面板
|
|
||||||
if (x !== undefined) {
|
if (x !== undefined) {
|
||||||
Editor.Ipc.sendToPanel("scene", "scene:set-property", {
|
node.x = Number(x);
|
||||||
id,
|
|
||||||
path: "x",
|
|
||||||
type: "Number",
|
|
||||||
value: Number(x),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
if (y !== undefined) {
|
if (y !== undefined) {
|
||||||
Editor.Ipc.sendToPanel("scene", "scene:set-property", {
|
node.y = Number(y);
|
||||||
id,
|
}
|
||||||
path: "y",
|
if (args.rotation !== undefined) {
|
||||||
type: "Number",
|
node.angle = Number(args.rotation);
|
||||||
value: Number(y),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
if (args.width !== undefined) {
|
if (args.width !== undefined) {
|
||||||
Editor.Ipc.sendToPanel("scene", "scene:set-property", {
|
node.width = Number(args.width);
|
||||||
id,
|
|
||||||
path: "width",
|
|
||||||
type: "Number",
|
|
||||||
value: Number(args.width),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
if (args.height !== undefined) {
|
if (args.height !== undefined) {
|
||||||
Editor.Ipc.sendToPanel("scene", "scene:set-property", {
|
node.height = Number(args.height);
|
||||||
id,
|
|
||||||
path: "height",
|
|
||||||
type: "Number",
|
|
||||||
value: Number(args.height),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
if (scaleX !== undefined) {
|
if (scaleX !== undefined) {
|
||||||
Editor.Ipc.sendToPanel("scene", "scene:set-property", {
|
node.scaleX = Number(scaleX);
|
||||||
id,
|
|
||||||
path: "scaleX",
|
|
||||||
type: "Number",
|
|
||||||
value: Number(scaleX),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
if (scaleY !== undefined) {
|
if (scaleY !== undefined) {
|
||||||
Editor.Ipc.sendToPanel("scene", "scene:set-property", {
|
node.scaleY = Number(scaleY);
|
||||||
id,
|
}
|
||||||
path: "scaleY",
|
if (args.anchorX !== undefined) {
|
||||||
type: "Number",
|
node.anchorX = Number(args.anchorX);
|
||||||
value: Number(scaleY),
|
}
|
||||||
});
|
if (args.anchorY !== undefined) {
|
||||||
|
node.anchorY = Number(args.anchorY);
|
||||||
}
|
}
|
||||||
if (color) {
|
if (color) {
|
||||||
const c = new cc.Color().fromHEX(color);
|
node.color = new cc.Color().fromHEX(color);
|
||||||
Editor.Ipc.sendToPanel("scene", "scene:set-property", {
|
}
|
||||||
id: id,
|
if (args.opacity !== undefined) {
|
||||||
path: "color",
|
node.opacity = Number(args.opacity);
|
||||||
type: "Color",
|
}
|
||||||
value: { r: c.r, g: c.g, b: c.b, a: c.a },
|
if (args.skewX !== undefined) {
|
||||||
});
|
node.skewX = Number(args.skewX);
|
||||||
|
}
|
||||||
|
if (args.skewY !== undefined) {
|
||||||
|
node.skewY = Number(args.skewY);
|
||||||
}
|
}
|
||||||
|
|
||||||
Editor.Ipc.sendToMain("scene:dirty");
|
Editor.Ipc.sendToMain("scene:dirty");
|
||||||
|
|||||||
Reference in New Issue
Block a user