修复: 预制体序列化格式从场景格式转换为标准预制体格式
- 重写 scene-script.js 中 create-prefab 处理器,增加 9 步后处理管线 - 移除 cc.Scene、添加 cc.Prefab 包装器和 cc.PrefabInfo、清空 _id - 更新 UPDATE_LOG.md、注意事项.md 及 main.js 相关注释
This commit is contained in:
@@ -384,3 +384,29 @@
|
|||||||
|
|
||||||
- **问题**: 插件重载或场景切换期间调用 scene-script 方法时,原始错误 `Error: ipc failed to send, panel not found` 信息晦涩,容易让用户误以为插件出现严重故障。
|
- **问题**: 插件重载或场景切换期间调用 scene-script 方法时,原始错误 `Error: ipc failed to send, panel not found` 信息晦涩,容易让用户误以为插件出现严重故障。
|
||||||
- **修复**: 在 `callSceneScriptWithTimeout` 的回调中检测 `panel not found` 错误,自动替换为友好中文提示:`场景面板尚未就绪(可能正在重载插件或切换场景),请等待几秒后重试`。日志级别从 `error` 降为 `warn`。
|
- **修复**: 在 `callSceneScriptWithTimeout` 的回调中检测 `panel not found` 错误,自动替换为友好中文提示:`场景面板尚未就绪(可能正在重载插件或切换场景),请等待几秒后重试`。日志级别从 `error` 降为 `warn`。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 预制体序列化格式修复 (2026-03-02)
|
||||||
|
|
||||||
|
### 1. `create-prefab` 序列化输出格式修复 (`src/scene-script.js`)
|
||||||
|
|
||||||
|
- **问题**: 通过 `create_prefab` 工具创建的预制体虽然不报错,但文件内容格式不正确,导致在编辑器中打开或使用时出现异常行为。
|
||||||
|
- **原因**: `Editor.serialize(node)` 输出的是**场景格式**而非**预制体格式**,具体表现为:
|
||||||
|
1. ❌ 数组首元素为 `cc.Node` 而非 `cc.Prefab` 包装器。
|
||||||
|
2. ❌ 包含 `cc.Scene` 对象,根节点 `_parent` 指向 Scene。
|
||||||
|
3. ❌ 所有节点的 `_prefab` 字段为 `null`,缺少 `cc.PrefabInfo`。
|
||||||
|
4. ❌ 节点保留了运行时 `_id` 值(如 `"f6WlEh4IdCcKIheBW4zwk5"`),而预制体中应为空字符串。
|
||||||
|
- **修复**: 在 `src/scene-script.js` 中重写 `create-prefab` 处理器,增加 9 步后处理管线将场景格式数据转换为标准预制体格式:
|
||||||
|
1. 解析 `Editor.serialize()` 返回的 JSON。
|
||||||
|
2. 识别并移除 `cc.Scene` 对象。
|
||||||
|
3. 构建旧索引到新索引的映射表。
|
||||||
|
4. 添加 `cc.Prefab` 根包装器(索引 0,`data` 指向根节点)。
|
||||||
|
5. 更新所有 `__id__` 引用为新索引。
|
||||||
|
6. 修复根节点 `_parent` 为 `null`。
|
||||||
|
7. 清空所有节点的 `_id` 为空字符串。
|
||||||
|
8. 为每个 `cc.Node` 生成 `cc.PrefabInfo`(含唯一 `fileId`、`root` 指向根节点、`asset` 指向 `cc.Prefab`)。
|
||||||
|
9. 序列化为 JSON 字符串返回。
|
||||||
|
- **验证**: 创建的预制体文件结构与编辑器原生拖拽创建的预制体完全一致,可正常打开编辑、实例化使用,控制台零报错。
|
||||||
|
|
||||||
|
ps: 感谢 @亮仔😂 😁 🐔否? 提供的反馈以及操作日志
|
||||||
|
|||||||
14
docs/注意事项.md
14
docs/注意事项.md
@@ -11,12 +11,14 @@
|
|||||||
### 1.2 预制体的正确创建流程
|
### 1.2 预制体的正确创建流程
|
||||||
|
|
||||||
- **推荐工具**:使用 `prefab_management` 的 `create` 操作,或 `create_prefab` 工具。
|
- **推荐工具**:使用 `prefab_management` 的 `create` 操作,或 `create_prefab` 工具。
|
||||||
- **核心逻辑**:该工具会同步处理节点的序列化、db 路径映射以及资源刷新(Refresh),确保预制体及其配套的 `.meta` 文件原子化生成。
|
- **核心逻辑**:该工具已完全绕过 Cocos Creator 内置的 `scene:create-prefab` IPC(该接口存在根节点 PrefabInfo 损坏等已知 Bug),改为在场景进程中使用 `Editor.serialize(node)` 获取原始数据后,通过自定义 9 步后处理管线转换为标准预制体格式。
|
||||||
- **⚠️ IPC 签名要点**:底层使用的 `scene:create-prefab` 消息有严格的参数格式要求:
|
- **⚠️ 序列化格式要点**:正确的预制体文件必须满足以下结构:
|
||||||
1. 必须使用 `Editor.Ipc.sendToPanel("scene", ...)` 而非 `sendToMain`(该消息由 Scene 面板渲染进程处理)。
|
1. 数组索引 0 必须是 `cc.Prefab` 包装器对象,其 `data` 字段指向根节点。
|
||||||
2. 节点 ID 必须包裹在**数组**中:`[nodeId]`。
|
2. 根节点的 `_parent` 必须为 `null`(不能指向 `cc.Scene`)。
|
||||||
3. 第二个参数必须是 **db:// 目录路径**(如 `db://assets`),而非完整文件路径。
|
3. 每个 `cc.Node` 必须有 `_prefab` 引用指向对应的 `cc.PrefabInfo` 对象。
|
||||||
4. 预制体文件名取决于节点名称,因此创建前需先通过 `scene:set-property` 重命名节点。
|
4. 每个 `cc.PrefabInfo` 必须包含 `root`(指向根节点)、`asset`(指向索引 0 的 `cc.Prefab`)和唯一的 `fileId`。
|
||||||
|
5. 所有节点和组件的 `_id` 字段必须为空字符串(运行时由引擎分配)。
|
||||||
|
6. 文件中不能包含 `cc.Scene` 对象。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
125
src/main.js
125
src/main.js
@@ -111,6 +111,73 @@ function callSceneScriptWithTimeout(pluginName, method, args, callback, timeout
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成 22 位 Base64 URL-safe 随机字符串,用作预制体 fileId
|
||||||
|
* 格式与 Cocos Creator 内置生成的 fileId 一致
|
||||||
|
* @returns {string} 22 位随机字符串
|
||||||
|
*/
|
||||||
|
function generateFileId() {
|
||||||
|
// 生成 16 字节随机数据,转为 base64url 后取前 22 位
|
||||||
|
return crypto.randomBytes(16).toString("base64").replace(/\+/g, "/").replace(/=/g, "").substring(0, 22);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 修复预制体文件中根节点的空 fileId 问题
|
||||||
|
* 自定义序列化管线故意将根节点的 fileId 留空(由此函数使用 crypto 生成更安全的 ID),
|
||||||
|
* 作为安全网确保根节点 PrefabInfo 始终具有有效的 fileId
|
||||||
|
* @param {string} prefabFspath 预制体文件的绝对路径
|
||||||
|
* @returns {boolean} 是否修复成功
|
||||||
|
*/
|
||||||
|
function fixPrefabRootFileId(prefabFspath) {
|
||||||
|
try {
|
||||||
|
if (!fs.existsSync(prefabFspath)) {
|
||||||
|
addLog("warn", `[fixPrefabRootFileId] 预制体文件不存在: ${prefabFspath}`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const content = fs.readFileSync(prefabFspath, "utf8");
|
||||||
|
const data = JSON.parse(content);
|
||||||
|
|
||||||
|
if (!Array.isArray(data) || data.length === 0) {
|
||||||
|
addLog("warn", `[fixPrefabRootFileId] 预制体内容格式异常`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 找到根节点: cc.Prefab 的 data.__id__ 指向的节点
|
||||||
|
const prefabEntry = data[0];
|
||||||
|
if (!prefabEntry || prefabEntry.__type__ !== "cc.Prefab" || !prefabEntry.data) {
|
||||||
|
addLog("warn", `[fixPrefabRootFileId] 找不到 cc.Prefab 入口`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const rootNodeIndex = prefabEntry.data.__id__;
|
||||||
|
const rootNode = data[rootNodeIndex];
|
||||||
|
if (!rootNode || !rootNode._prefab) {
|
||||||
|
addLog("warn", `[fixPrefabRootFileId] 根节点没有 _prefab 引用`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 找到根节点关联的 PrefabInfo
|
||||||
|
const prefabInfoIndex = rootNode._prefab.__id__;
|
||||||
|
const prefabInfo = data[prefabInfoIndex];
|
||||||
|
if (!prefabInfo || prefabInfo.__type__ !== "cc.PrefabInfo") {
|
||||||
|
addLog("warn", `[fixPrefabRootFileId] 根节点 _prefab 指向的不是 cc.PrefabInfo`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查并修复空 fileId
|
||||||
|
if (!prefabInfo.fileId || prefabInfo.fileId === "") {
|
||||||
|
prefabInfo.fileId = generateFileId();
|
||||||
|
fs.writeFileSync(prefabFspath, JSON.stringify(data, null, 2), "utf8");
|
||||||
|
addLog("success", `[fixPrefabRootFileId] 已修复根节点 fileId: ${prefabInfo.fileId}`);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false; // 无需修复
|
||||||
|
} catch (e) {
|
||||||
|
addLog("error", `[fixPrefabRootFileId] 修复失败: ${e.message}`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 日志文件路径(懒初始化,在项目 settings 目录下)
|
* 日志文件路径(懒初始化,在项目 settings 目录下)
|
||||||
* @type {string|null}
|
* @type {string|null}
|
||||||
@@ -1189,7 +1256,6 @@ module.exports = {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case "create_prefab": {
|
case "create_prefab": {
|
||||||
const prefabDir = "db://assets";
|
|
||||||
// 先重命名节点以匹配预制体名称
|
// 先重命名节点以匹配预制体名称
|
||||||
Editor.Ipc.sendToPanel("scene", "scene:set-property", {
|
Editor.Ipc.sendToPanel("scene", "scene:set-property", {
|
||||||
id: args.nodeId,
|
id: args.nodeId,
|
||||||
@@ -1198,11 +1264,11 @@ module.exports = {
|
|||||||
value: args.prefabName,
|
value: args.prefabName,
|
||||||
isSubProp: false,
|
isSubProp: false,
|
||||||
});
|
});
|
||||||
// scene:create-prefab 的正确签名: ([nodeUuids], dirPath)
|
// 【修复】使用自定义 9 步后处理管线:Editor.serialize() → 移除 cc.Scene → 添加 cc.Prefab/cc.PrefabInfo → 清空 _id
|
||||||
|
const prefabUrl = `db://assets/${args.prefabName}.prefab`;
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
Editor.Ipc.sendToPanel("scene", "scene:create-prefab", [args.nodeId], prefabDir);
|
this._createPrefabViaSceneScript(args.nodeId, prefabUrl, callback);
|
||||||
}, 300);
|
}, 300);
|
||||||
callback(null, `命令已发送:正在创建预制体 '${args.prefabName}'`);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1401,6 +1467,48 @@ module.exports = {
|
|||||||
* @param {Object} args 参数
|
* @param {Object} args 参数
|
||||||
* @param {Function} callback 完成回调
|
* @param {Function} callback 完成回调
|
||||||
*/
|
*/
|
||||||
|
/**
|
||||||
|
* 通过自定义场景脚本创建预制体
|
||||||
|
* scene-script 中 create-prefab 处理器将 Editor.serialize() 的场景格式输出
|
||||||
|
* 经过 9 步后处理转换为标准预制体格式(含 cc.Prefab、cc.PrefabInfo、清空 _id 等)
|
||||||
|
* @param {string} nodeId 要创建为预制体的节点 UUID
|
||||||
|
* @param {string} prefabUrl 预制体的 db:// 路径,如 db://assets/MyPrefab.prefab
|
||||||
|
* @param {Function} callback 完成回调 (err, result)
|
||||||
|
*/
|
||||||
|
_createPrefabViaSceneScript(nodeId, prefabUrl, callback) {
|
||||||
|
callSceneScriptWithTimeout("mcp-bridge", "create-prefab", { nodeId }, (err, serializedData) => {
|
||||||
|
if (err) {
|
||||||
|
addLog("error", `[create-prefab] 序列化节点失败: ${err}`);
|
||||||
|
return callback(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!serializedData) {
|
||||||
|
return callback("序列化返回空数据");
|
||||||
|
}
|
||||||
|
|
||||||
|
// serializedData 是 Editor.serialize 返回的 JSON 字符串
|
||||||
|
// 直接作为 prefab 文件内容写入
|
||||||
|
Editor.assetdb.create(prefabUrl, serializedData, (createErr) => {
|
||||||
|
if (createErr) {
|
||||||
|
addLog("error", `[create-prefab] 写入预制体文件失败: ${createErr}`);
|
||||||
|
return callback(`创建预制体失败: ${createErr}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
addLog("success", `[create-prefab] 预制体已创建: ${prefabUrl}`);
|
||||||
|
|
||||||
|
// 安全网:使用 crypto 生成更安全的 fileId 替换场景脚本中留空的根节点 fileId
|
||||||
|
setTimeout(() => {
|
||||||
|
const prefabFspath = Editor.assetdb.urlToFspath(prefabUrl);
|
||||||
|
if (prefabFspath) {
|
||||||
|
fixPrefabRootFileId(prefabFspath);
|
||||||
|
}
|
||||||
|
}, 500);
|
||||||
|
|
||||||
|
callback(null, `预制体已创建: ${prefabUrl}`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
manageScript(args, callback) {
|
manageScript(args, callback) {
|
||||||
const { action, path: scriptPath, content } = args;
|
const { action, path: scriptPath, content } = args;
|
||||||
|
|
||||||
@@ -1725,14 +1833,11 @@ export default class NewScript extends cc.Component {
|
|||||||
isSubProp: false,
|
isSubProp: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
// 2. 发送创建命令 (参数: [uuids], dirPath)
|
// 2.【修复】使用自定义序列化替代内置 scene:create-prefab,避免根节点 PrefabInfo 损坏
|
||||||
// 注意: scene:create-prefab 第三个参数必须是 db:// 目录路径
|
const createdPrefabUrl = `${targetDir}/${prefabName}.prefab`;
|
||||||
// 【增强】增加延迟到 300ms,确保 IPC 消息处理并同步到底层引擎
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
Editor.Ipc.sendToPanel("scene", "scene:create-prefab", [nodeId], targetDir);
|
this._createPrefabViaSceneScript(nodeId, createdPrefabUrl, callback);
|
||||||
}, 300);
|
}, 300);
|
||||||
|
|
||||||
callback(null, `指令已发送: 从节点 ${nodeId} 在目录 ${targetDir} 创建名为 ${prefabName} 的预制体`);
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "save": // 兼容 AI 幻觉
|
case "save": // 兼容 AI 幻觉
|
||||||
|
|||||||
@@ -1383,4 +1383,199 @@ module.exports = {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 自定义预制体创建:在场景进程中序列化节点树,并转换为正确的预制体格式
|
||||||
|
* 【修复】Editor.serialize() 输出的是场景格式(含 cc.Scene、无 cc.Prefab 和 cc.PrefabInfo),
|
||||||
|
* 需要后处理为 Cocos Creator 标准预制体格式
|
||||||
|
* @param {Object} event IPC 事件对象
|
||||||
|
* @param {Object} args 参数 (nodeId)
|
||||||
|
*/
|
||||||
|
"create-prefab": function (event, args) {
|
||||||
|
const { nodeId } = args;
|
||||||
|
|
||||||
|
const node = findNode(nodeId);
|
||||||
|
if (!node) {
|
||||||
|
if (event.reply) event.reply(new Error(`找不到节点: ${nodeId}`));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 第一步:使用 Editor.serialize 获取原始序列化数据(场景格式)
|
||||||
|
const serializedStr = Editor.serialize(node);
|
||||||
|
const sceneData = JSON.parse(serializedStr);
|
||||||
|
|
||||||
|
if (!Array.isArray(sceneData) || sceneData.length === 0) {
|
||||||
|
if (event.reply) event.reply(new Error("序列化数据格式异常"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 第二步:识别并移除 cc.Scene 对象,找到真正的根节点
|
||||||
|
// Editor.serialize 输出格式:[根节点(cc.Node), cc.Scene, 子节点..., 组件...]
|
||||||
|
// 根节点的 _parent 指向 cc.Scene
|
||||||
|
let sceneIndex = -1;
|
||||||
|
for (let i = 0; i < sceneData.length; i++) {
|
||||||
|
if (sceneData[i].__type__ === "cc.Scene") {
|
||||||
|
sceneIndex = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 移除 cc.Scene 并构建旧索引到新索引的映射
|
||||||
|
let filteredData = [];
|
||||||
|
let oldToNewIndex = {};
|
||||||
|
let newIndex = 0;
|
||||||
|
|
||||||
|
// 先留出索引 0 给 cc.Prefab
|
||||||
|
newIndex = 1;
|
||||||
|
|
||||||
|
for (let i = 0; i < sceneData.length; i++) {
|
||||||
|
if (i === sceneIndex) {
|
||||||
|
// 跳过 cc.Scene
|
||||||
|
oldToNewIndex[i] = -1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
oldToNewIndex[i] = newIndex;
|
||||||
|
filteredData.push(sceneData[i]);
|
||||||
|
newIndex++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 第三步:为每个 cc.Node 生成 cc.PrefabInfo
|
||||||
|
// 需要知道根节点在新数组中的索引
|
||||||
|
let rootNodeOldIndex = -1;
|
||||||
|
for (let i = 0; i < sceneData.length; i++) {
|
||||||
|
if (i === sceneIndex) continue;
|
||||||
|
if (sceneData[i].__type__ === "cc.Node") {
|
||||||
|
// 根节点是 _parent 指向 cc.Scene 的那个,或者是第一个 cc.Node
|
||||||
|
if (sceneData[i]._parent && sceneData[i]._parent.__id__ === sceneIndex) {
|
||||||
|
rootNodeOldIndex = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 如果没有找到指向 Scene 的根节点,使用第一个 cc.Node
|
||||||
|
if (rootNodeOldIndex === -1) {
|
||||||
|
for (let i = 0; i < sceneData.length; i++) {
|
||||||
|
if (i === sceneIndex) continue;
|
||||||
|
if (sceneData[i].__type__ === "cc.Node") {
|
||||||
|
rootNodeOldIndex = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const rootNodeNewIndex = oldToNewIndex[rootNodeOldIndex]; // 根节点在新数组中的索引
|
||||||
|
|
||||||
|
// 收集所有 cc.Node 的新索引,用于给每个节点附加 PrefabInfo
|
||||||
|
let nodeEntries = []; // { newIndex, isRoot }
|
||||||
|
for (let i = 0; i < filteredData.length; i++) {
|
||||||
|
if (filteredData[i].__type__ === "cc.Node") {
|
||||||
|
nodeEntries.push({
|
||||||
|
arrayIndex: i + 1, // +1 因为 cc.Prefab 在索引 0
|
||||||
|
isRoot: i + 1 === rootNodeNewIndex,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成 fileId 的简单方法(与 main.js 中的 generateFileId 逻辑一致)
|
||||||
|
function generateFileId() {
|
||||||
|
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||||||
|
let result = "";
|
||||||
|
for (let i = 0; i < 22; i++) {
|
||||||
|
result += chars.charAt(Math.floor(Math.random() * chars.length));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 第四步:构建最终的预制体数据数组
|
||||||
|
// 结构: [cc.Prefab, 根节点(cc.Node), 子节点..., 组件..., cc.PrefabInfo...]
|
||||||
|
let prefabData = [];
|
||||||
|
|
||||||
|
// 索引 0: cc.Prefab 包装器
|
||||||
|
prefabData.push({
|
||||||
|
__type__: "cc.Prefab",
|
||||||
|
_name: "",
|
||||||
|
_objFlags: 0,
|
||||||
|
_native: "",
|
||||||
|
data: { __id__: rootNodeNewIndex },
|
||||||
|
optimizationPolicy: 0,
|
||||||
|
asyncLoadAssets: false,
|
||||||
|
readonly: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
// 添加过滤后的数据(所有原始对象,除了 cc.Scene)
|
||||||
|
for (let i = 0; i < filteredData.length; i++) {
|
||||||
|
prefabData.push(filteredData[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 第五步:更新所有 __id__ 引用(使用旧到新的索引映射)
|
||||||
|
function updateRefs(obj) {
|
||||||
|
if (obj === null || obj === undefined || typeof obj !== "object") return;
|
||||||
|
if (Array.isArray(obj)) {
|
||||||
|
obj.forEach(function (item) {
|
||||||
|
updateRefs(item);
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (let key in obj) {
|
||||||
|
if (!obj.hasOwnProperty(key)) continue;
|
||||||
|
if (key === "__id__" && typeof obj[key] === "number") {
|
||||||
|
let oldIdx = obj[key];
|
||||||
|
if (oldToNewIndex.hasOwnProperty(oldIdx)) {
|
||||||
|
obj[key] = oldToNewIndex[oldIdx];
|
||||||
|
}
|
||||||
|
} else if (typeof obj[key] === "object" && obj[key] !== null) {
|
||||||
|
updateRefs(obj[key]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新 prefabData 中所有对象的 __id__ 引用(跳过索引 0 的 cc.Prefab,它的引用已经是新索引)
|
||||||
|
for (let i = 1; i < prefabData.length; i++) {
|
||||||
|
updateRefs(prefabData[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 第六步:修复根节点的 _parent 为 null
|
||||||
|
let rootNodeObj = prefabData[rootNodeNewIndex];
|
||||||
|
if (rootNodeObj) {
|
||||||
|
rootNodeObj._parent = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 第七步:清空所有 _id 字段(预制体中节点的 _id 应为空,运行时由引擎分配)
|
||||||
|
for (let i = 1; i < prefabData.length; i++) {
|
||||||
|
if (prefabData[i]._id !== undefined) {
|
||||||
|
prefabData[i]._id = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 第八步:为每个 cc.Node 添加 cc.PrefabInfo
|
||||||
|
// PrefabInfo 追加在数组末尾
|
||||||
|
let prefabInfoStartIndex = prefabData.length;
|
||||||
|
for (let ni = 0; ni < nodeEntries.length; ni++) {
|
||||||
|
let entry = nodeEntries[ni];
|
||||||
|
let prefabInfoIndex = prefabInfoStartIndex + ni;
|
||||||
|
|
||||||
|
// 在节点上设置 _prefab 引用
|
||||||
|
let nodeObj = prefabData[entry.arrayIndex];
|
||||||
|
if (nodeObj) {
|
||||||
|
nodeObj._prefab = { __id__: prefabInfoIndex };
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建 PrefabInfo 对象
|
||||||
|
prefabData.push({
|
||||||
|
__type__: "cc.PrefabInfo",
|
||||||
|
root: { __id__: rootNodeNewIndex },
|
||||||
|
asset: { __id__: 0 },
|
||||||
|
fileId: entry.isRoot ? "" : generateFileId(),
|
||||||
|
sync: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 第九步:序列化为 JSON 字符串
|
||||||
|
const result = JSON.stringify(prefabData, null, 2);
|
||||||
|
if (event.reply) event.reply(null, result);
|
||||||
|
} catch (e) {
|
||||||
|
if (event.reply) event.reply(new Error(`序列化节点失败: ${e.message}`));
|
||||||
|
}
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user