From 7f4e447ba052e4ddcd77da91bbd933512d46ede4 Mon Sep 17 00:00:00 2001 From: furao Date: Fri, 12 Jun 2026 10:42:39 +0800 Subject: [PATCH] =?UTF-8?q?[cli]=20=E6=96=B0=E5=A2=9E:=20add-spine-socket?= =?UTF-8?q?=20op=EF=BC=88=E7=BB=91=E5=AE=9A=20Spine=20Socket=20=E5=88=B0?= =?UTF-8?q?=E8=8A=82=E7=82=B9=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- QUICK-REF.md | 1 + cli/src/cli/help.js | 1 + cli/src/editor/index.js | 2 ++ cli/src/editor/op-schema.js | 2 ++ cli/src/editor/ops/add-spine-socket.js | 50 ++++++++++++++++++++++++++ cli/src/primitives.js | 17 +++++++++ doc/cli.md | 1 + 7 files changed, 74 insertions(+) create mode 100644 cli/src/editor/ops/add-spine-socket.js diff --git a/QUICK-REF.md b/QUICK-REF.md index 3336f5c..c1523c6 100644 --- a/QUICK-REF.md +++ b/QUICK-REF.md @@ -70,6 +70,7 @@ node extensions/cc-3-8-x-mcp/cli/bin/cocos-mcp-cli.js |---|---| | 加节点 | `add-node` | | 加嵌套 prefab 实例(stub) | `add-nested-prefab`(parent + prefabUuid + name? + lpos?) | +| 给 Spine 增加/更新 socket 绑定 | `add-spine-socket`(node + path + target;同 path 幂等更新 target) | | 替换嵌套 prefab 的 asset uuid(保留 stub 结构) | `replace-nested-prefab`(target + prefabUuid + clearOverrides?) | | 删节点 | `remove-node` | | 清悬空嵌套实例根(删了一半的 prefab 残留:父引用没了但根 PrefabInfo 登记还在,残留 asset 仍被加载 → 404)| `sync-nested-roots`(无参,重建根 nestedPrefabInstanceRoots) | diff --git a/cli/src/cli/help.js b/cli/src/cli/help.js index 34816f3..3aacb88 100644 --- a/cli/src/cli/help.js +++ b/cli/src/cli/help.js @@ -51,6 +51,7 @@ Supported ops: reorder-children # 调子节点顺序(影响渲染层级) bulk-set # 按 selector(byComponent/byNamePrefix/byNameRegex)一次改一批 add-node / remove-node / clone-node + add-spine-socket # 给 sp.Skeleton 增加/更新 socket 绑定 add-component / set-component-ref # componentType 支持 @ccclass 名或压缩 classId # set-component-ref 的 refSubNode 可用字符串数组走多层嵌套 stub set-nested-component-field # 仅 stub 节点(嵌套 prefab)改组件字段 diff --git a/cli/src/editor/index.js b/cli/src/editor/index.js index 6d37192..a13b327 100644 --- a/cli/src/editor/index.js +++ b/cli/src/editor/index.js @@ -45,6 +45,7 @@ const { execSetSprite } = require('./ops/set-sprite.js'); const { execSetNodeColor } = require('./ops/set-node-color.js'); const { execReplaceNestedPrefab } = require('./ops/replace-nested-prefab.js'); const { execAddNestedPrefab } = require('./ops/add-nested-prefab.js'); +const { execAddSpineSocket } = require('./ops/add-spine-socket.js'); const { execResetOverrides } = require('./ops/reset-overrides.js'); const { execEnsureMeta } = require('./ops/ensure-meta.js'); const { execSyncNestedRoots } = require('./ops/sync-nested-roots.js'); @@ -81,6 +82,7 @@ const OP_HANDLERS = { 'set-node-color': execSetNodeColor, 'replace-nested-prefab': execReplaceNestedPrefab, 'add-nested-prefab': execAddNestedPrefab, + 'add-spine-socket': execAddSpineSocket, 'reset-overrides': execResetOverrides, 'ensure-meta': execEnsureMeta, 'sync-nested-roots': execSyncNestedRoots, diff --git a/cli/src/editor/op-schema.js b/cli/src/editor/op-schema.js index 1ffb74d..5f872fa 100644 --- a/cli/src/editor/op-schema.js +++ b/cli/src/editor/op-schema.js @@ -88,6 +88,7 @@ const T = { constraintNum: 'number', placeholder: 'string', string: 'string', + path: 'string', labelNode: 'string', spriteNode: 'string', subNode: 'any', // string | string[] @@ -129,6 +130,7 @@ const SCHEMAS = { 'set-node-color': { required: ['node'], optional: ['r', 'g', 'b', 'a'] }, 'replace-nested-prefab': { required: ['target', 'prefabUuid'], optional: ['clearOverrides'] }, 'add-nested-prefab': { required: ['parent', 'prefabUuid'], optional: ['name', 'lpos'] }, + 'add-spine-socket': { required: ['node', 'path', 'target'], optional: [] }, 'reset-overrides': { required: ['node'], optional: ['property', 'componentType', 'subNode', 'all'] }, // ensure-meta: 给 .ts/.json 文件创建 .meta(v4 uuid),让后续 className → classId 查表能命中 'ensure-meta': { required: ['path'], optional: [], typeOverrides: { path: 'string' } }, diff --git a/cli/src/editor/ops/add-spine-socket.js b/cli/src/editor/ops/add-spine-socket.js new file mode 100644 index 0000000..af35be3 --- /dev/null +++ b/cli/src/editor/ops/add-spine-socket.js @@ -0,0 +1,50 @@ +// add-spine-socket: 给普通节点上的 sp.Skeleton 增加 / 更新 Spine socket 绑定 +// op: { op:'add-spine-socket', node, path, target } +// +// - node: 挂 sp.Skeleton 的节点 +// - path: Spine socket path,例如 "root/zk/tou2" +// - target: 要绑定到该 socket 的 cc.Node +// - 幂等:同 path 已存在时只更新 target,不新增重复 socket 对象 + +'use strict'; + +const { makeSpineSocket } = require('../../primitives.js'); +const { isStub, resolveNode, findComponent } = require('../helpers.js'); + +function execAddSpineSocket(prefabData, op) { + const { elements } = prefabData; + const { node: nodeSelector, path, target: targetSelector } = op; + + if (typeof path !== 'string' || path.length === 0) { + throw new Error(`editPrefab [add-spine-socket]: path 必须是非空字符串`); + } + + const { node, nodeId } = resolveNode(prefabData, nodeSelector, 'add-spine-socket'); + if (isStub(elements, node)) { + throw new Error(`editPrefab [add-spine-socket]: 节点 "${node._name}" 是 stub 代理,不支持直接改子 prefab 的 sp.Skeleton`); + } + + const skeleton = findComponent(elements, node, 'sp.Skeleton'); + if (!skeleton) { + throw new Error(`editPrefab [add-spine-socket]: 节点 "${node._name}" 上找不到 sp.Skeleton 组件`); + } + + const { nodeId: targetId } = resolveNode(prefabData, targetSelector, 'add-spine-socket target'); + + if (!Array.isArray(skeleton._sockets)) skeleton._sockets = []; + for (const socketRef of skeleton._sockets) { + if (!socketRef || typeof socketRef.__id__ !== 'number') continue; + const socket = elements[socketRef.__id__]; + if (socket && socket.__type__ === 'sp.Skeleton.SpineSocket' && socket.path === path) { + socket.target = { __id__: targetId }; + return nodeId; + } + } + + const socketId = elements.length; + elements.push(makeSpineSocket({ path, targetId })); + skeleton._sockets.push({ __id__: socketId }); + return nodeId; +} + +module.exports = { execAddSpineSocket }; diff --git a/cli/src/primitives.js b/cli/src/primitives.js index e22ea80..ecc393b 100644 --- a/cli/src/primitives.js +++ b/cli/src/primitives.js @@ -391,6 +391,22 @@ function makeSpSkeleton(opts) { return obj; } +/** + * 构造 sp.Skeleton.SpineSocket 裸对象。 + * @param {object} opts + * @param {string} opts.path - Spine bone/slot path,例如 root/zk/tou2 + * @param {number} opts.targetId - 绑定的 cc.Node __id__ + * @returns {object} + */ +function makeSpineSocket(opts) { + const { path, targetId } = opts; + return { + __type__: 'sp.Skeleton.SpineSocket', + path, + target: ref(targetId), + }; +} + // ───────────────────────────────────────────── // cc.PrefabInfo / cc.CompPrefabInfo // ───────────────────────────────────────────── @@ -469,6 +485,7 @@ module.exports = { makeLabel, makeWidget, makeSpSkeleton, + makeSpineSocket, // Prefab 元信息 makePrefabInfo, makeCompPrefabInfo, diff --git a/doc/cli.md b/doc/cli.md index 6f57d00..c2bd941 100644 --- a/doc/cli.md +++ b/doc/cli.md @@ -360,6 +360,7 @@ stub 节点(嵌套 prefab 实例)在 prefab JSON 里 `_name = ""`(空字 | op | 参数 | 说明 | |---|---|---| | `add-node` | `parent`, `node: {name, lpos?, active?, components?, width?, height?, anchor?}` | 新增 cc.Node;parent 是 stub 时走 mountedChildren。`components: ["UITransform"]` 自动建 UITransform | +| `add-spine-socket` | `node`, `path`, `target` | 给普通节点上的 `sp.Skeleton` 新增 / 更新 `sp.Skeleton.SpineSocket`。`path` 是 Spine socket path(如 `root/zk/tou2`),`target` 是绑定节点;同 path 幂等更新 target,不重复追加 | | `remove-node` | `target` | 从父节点移除引用;元素本身保留(orphan),保持其他 `__id__` 稳定 | | `sync-nested-roots` | (无) | 重建根 `PrefabInfo.nestedPrefabInstanceRoots`,剔除「删了一半」残留的悬空嵌套实例根(节点 `_parent` 已移除但根登记残留 → 残留嵌套 prefab 的 asset 仍被当依赖加载,运行时 404)。只重写该数组,不删 elements、不动其他 `__id__`、不产生 null 槽;被孤立的残留对象成为不可达 orphan。复用 remove-node 内部同名逻辑 | | `clone-node` | `source`, `parent`, `name` | 深拷贝整棵子树,分配新 `__id__` + 新 fileId |