import path from 'path'; const Fs = require('fs'); // ── 属性映射:标记 → MapGrid 组件全部 8 个属性的完整值 ── const MARKER_PROPS: Record> = { '': { _isCanWalk: true, _isCanPutGrid: true, _isBestPlace: false, _isCanBeDig: false, _isCanPutText: false, _isCanRemove: false, _isCanBeThunder: false, _isFreePlace: false }, 'CanWalk': { _isCanWalk: true, _isCanPutGrid: true, _isBestPlace: true, _isCanBeDig: false, _isCanPutText: false, _isCanRemove: false, _isCanBeThunder: false, _isFreePlace: false }, 'CanBeDig': { _isCanWalk: false, _isCanPutGrid: false, _isBestPlace: false, _isCanBeDig: true, _isCanPutText: false, _isCanRemove: false, _isCanBeThunder: false, _isFreePlace: false }, 'CanPut': { _isCanWalk: false, _isCanPutGrid: false, _isBestPlace: false, _isCanBeDig: false, _isCanPutText: true, _isCanRemove: false, _isCanBeThunder: false, _isFreePlace: false }, 'spawnPoint_1': { _isCanWalk: true, _isCanPutGrid: false, _isBestPlace: false, _isCanBeDig: false, _isCanPutText: false, _isCanRemove: false, _isCanBeThunder: false, _isFreePlace: false }, 'spawnPoint_2': { _isCanWalk: true, _isCanPutGrid: false, _isBestPlace: false, _isCanBeDig: false, _isCanPutText: false, _isCanRemove: false, _isCanBeThunder: false, _isFreePlace: false }, 'spawnPoint_3': { _isCanWalk: true, _isCanPutGrid: false, _isBestPlace: false, _isCanBeDig: false, _isCanPutText: false, _isCanRemove: false, _isCanBeThunder: false, _isFreePlace: false }, 'wallTarget1': { _isCanWalk: true, _isCanPutGrid: false, _isBestPlace: false, _isCanBeDig: false, _isCanPutText: false, _isCanRemove: false, _isCanBeThunder: false, _isFreePlace: false }, 'wallTarget2': { _isCanWalk: true, _isCanPutGrid: false, _isBestPlace: false, _isCanBeDig: false, _isCanPutText: false, _isCanRemove: false, _isCanBeThunder: false, _isFreePlace: false }, 'isFreePlace': { _isCanWalk: true, _isCanPutGrid: true, _isBestPlace: false, _isCanBeDig: false, _isCanPutText: false, _isCanRemove: false, _isCanBeThunder: false, _isFreePlace: true }, }; const SPAWN_MARKERS = ['spawnPoint_1', 'spawnPoint_2', 'spawnPoint_3']; const MAPGRID_FILE_ID = 'a5Czjqnj1DIa/1qXv6uwzF'; const CELL_SIZE = 93; const ORIGIN_X = -325.5; const ORIGIN_Y = 418.5; const COLS = 8; const ROWS = 10; function cellToWorld(index: number): { x: number; y: number } { const col = index % COLS; const row = Math.floor(index / COLS); return { x: ORIGIN_X + col * CELL_SIZE, y: ORIGIN_Y - row * CELL_SIZE }; } /** 在 JSON 数组中按 _name 查找节点,返回索引 */ function findNodeIndex(data: any[], name: string): number { for (let i = 0; i < data.length; i++) { if (data[i] && data[i]._name === name) return i; } return -1; } /** 获取格子的 PrefabInstance 对象 */ function getGridInstance(data: any[], geziIdx: number, gridIndex: number): any | null { const gezi = data[geziIdx]; if (!gezi || !gezi._children || gridIndex >= gezi._children.length) return null; const gridRef = gezi._children[gridIndex]; const gridNode = data[gridRef.__id__]; if (!gridNode || !gridNode._prefab) return null; const prefabInfo = data[gridNode._prefab.__id__]; if (!prefabInfo || !prefabInfo.instance) return null; return data[prefabInfo.instance.__id__]; } /** 清除指定格子中所有 MapGrid 组件的属性覆盖 */ function clearGridOverrides(data: any[], geziIdx: number, gridIndex: number): void { const instance = getGridInstance(data, geziIdx, gridIndex); if (!instance || !instance.propertyOverrides) return; instance.propertyOverrides = instance.propertyOverrides.filter((ref: any) => { const po = data[ref.__id__]; if (!po || !po.targetInfo) return true; // 保留非 targetInfo 的条目 const ti = data[po.targetInfo.__id__]; if (!ti || !ti.localID) return true; // 删除 targetInfo.localID 匹配 MapGrid 组件的覆盖 return ti.localID[0] !== MAPGRID_FILE_ID; }); } /** 向指定格子的 PrefabInstance 追加一条 MapGrid 属性覆盖 */ function addGridOverride(data: any[], geziIdx: number, gridIndex: number, propName: string, value: any): void { const instance = getGridInstance(data, geziIdx, gridIndex); if (!instance || !instance.propertyOverrides) return; const targetInfoId = data.length; data.push({ __type__: 'cc.TargetInfo', localID: [MAPGRID_FILE_ID] }); const overrideId = data.length; data.push({ __type__: 'CCPropertyOverrideInfo', targetInfo: { __id__: targetInfoId }, propertyPath: [propName], value: value, }); instance.propertyOverrides.push({ __id__: overrideId }); } /** 移动/设置指定父节点下某个子节点的坐标 */ function setChildPos(data: any[], parentName: string, childName: string, x: number, y: number): boolean { const parentIdx = findNodeIndex(data, parentName); if (parentIdx < 0) return false; const parent = data[parentIdx]; if (!parent._children) return false; for (const ref of parent._children) { const child = data[ref.__id__]; if (child && child._name === childName) { child._lpos = { __type__: 'cc.Vec3', x, y, z: 0 }; return true; } } return false; } /** 从父节点中删除指定名称的子节点 */ function removeChildByName(data: any[], parentName: string, childName: string): void { const parentIdx = findNodeIndex(data, parentName); if (parentIdx < 0) return; const parent = data[parentIdx]; if (!parent._children) return; parent._children = parent._children.filter((ref: any) => { const child = data[ref.__id__]; return !(child && child._name === childName); }); } /** 向父节点添加一个浅拷贝自模板的子节点(用于补充缺失的 spawnPoint) */ function copyChildFromTemplate(data: any[], template: any[], parentName: string, childName: string): void { const parentIdx = findNodeIndex(data, parentName); if (parentIdx < 0) return; // 从模板中找到同名子节点引用 const tplParentIdx = findNodeIndex(template, parentName); if (tplParentIdx < 0) return; const tplParent = template[tplParentIdx]; for (const ref of tplParent._children || []) { const child = template[ref.__id__]; if (child && child._name === childName) { // 将子节点及其组件深克隆追加到 data 末尾,并在父节点 _children 中引用 const cloneStartIdx = data.length; const cloned = JSON.parse(JSON.stringify(child)); data.push(cloned); data[parentIdx]._children.push({ __id__: cloneStartIdx }); return; } } } /** * 应用格子数据到预制体 JSON(新增和更新共用) */ function applyGridData(data: any[], template: any[], grids: string[]): void { const geziIdx = findNodeIndex(data, '格子'); if (geziIdx < 0) return; // 1. 收集 spawn 位置映射 + 遍历格子清除旧覆盖 / 添加新覆盖 const spawnPositions = new Map(); for (let i = 0; i < 80; i++) { clearGridOverrides(data, geziIdx, i); const marker = grids[i] || ''; const props = MARKER_PROPS[marker]; if (props) { for (const [prop, value] of Object.entries(props)) { addGridOverride(data, geziIdx, i, prop, value); } } if (SPAWN_MARKERS.includes(marker)) { spawnPositions.set(marker, cellToWorld(i)); } } // 2. wallTarget1/wallTarget2 配对 const t1 = grids.indexOf('wallTarget1'); const t2 = grids.indexOf('wallTarget2'); if (t1 >= 0 && t2 >= 0) { const p1 = cellToWorld(t1), p2 = cellToWorld(t2); const r1 = Math.floor(t1 / COLS), c1 = t1 % COLS; const r2 = Math.floor(t2 / COLS), c2 = t2 % COLS; let mx: number, my: number; if (r1 === r2) { mx = (p1.x + p2.x) / 2; my = p1.y; } // 横连 else if (c1 === c2) { mx = p1.x; my = (p1.y + p2.y) / 2; } // 竖连 else { mx = (p1.x + p2.x) / 2; my = (p1.y + p2.y) / 2; } // 斜角 setChildPos(data, '目标点', 'wallTarget1', mx, my); } else if (t1 >= 0) { const pos = cellToWorld(t1); setChildPos(data, '目标点', 'wallTarget1', pos.x, pos.y); } // 3. 同步 spawn 节点:删除多余 → 补回缺失 → 设置坐标 const allSpawns = ['spawnPoint_1', 'spawnPoint_2', 'spawnPoint_3', 'spawnPoint_4', 'spawnPoint_5', 'spawnPoint_6', 'spawnPoint_7']; for (const spName of allSpawns) { if (spawnPositions.has(spName)) continue; removeChildByName(data, '出怪口', spName); } for (const [spName, pos] of spawnPositions) { const parentIdx = findNodeIndex(data, '出怪口'); const exists = parentIdx >= 0 && (data[parentIdx]._children || []).some((ref: any) => { const child = data[ref.__id__]; return child && child._name === spName; }); if (!exists) { copyChildFromTemplate(data, template, '出怪口', spName); } setChildPos(data, '出怪口', spName, pos.x, pos.y); } } // ── 扩展入口 ── export const methods: { [key: string]: (...args: any[]) => any } = { async generateMapLayers() { try { const projectPath = Editor.Project.path; // 1. 读取 Excel const excelPath = path.join(projectPath, 'assets/res/地图/地图.xls'); if (!Fs.existsSync(excelPath)) { console.error('[地图生成] Excel 文件不存在:', excelPath); return; } const xlsxPath = path.join(__dirname, '../../excel-to-json-ts/lib/xlsx.js'); const XLSX = require(xlsxPath); const buffer = Fs.readFileSync(excelPath); const workbook = XLSX.read(buffer, { type: 'buffer' }); // 2. 读取模板 const templatePath = path.join(projectPath, 'assets/bundles/battleMap/prefab/地图/mapLayerTemplate.prefab'); if (!Fs.existsSync(templatePath)) { console.error('[地图生成] 模板预制体不存在:', templatePath); return; } const templateJson = Fs.readFileSync(templatePath, 'utf-8'); const templateData = JSON.parse(templateJson); const prefabDir = path.join(projectPath, 'assets/bundles/battleMap/prefab/地图'); console.log(`[地图生成] 开始处理 ${workbook.SheetNames.length} 个工作表...`); // 3. 遍历每个工作表 for (const sheetName of workbook.SheetNames) { const worksheet = workbook.Sheets[sheetName]; const rows: any[][] = XLSX.utils.sheet_to_json(worksheet, { header: 1 }); const grids: string[] = []; for (let r = 0; r < ROWS; r++) { for (let c = 0; c < COLS; c++) { const raw = (rows[r] || [])[c]; grids.push((raw !== undefined && raw !== null && raw !== '') ? String(raw).trim() : ''); } } const prefabPath = path.join(prefabDir, `${sheetName}.prefab`); const exists = Fs.existsSync(prefabPath); let prefabData: any[]; if (exists) { // 已有预制体 → 读取后原地修改 const existingJson = Fs.readFileSync(prefabPath, 'utf-8'); prefabData = JSON.parse(existingJson); applyGridData(prefabData, templateData, grids); } else { // 新预制体 → 从模板克隆生成 prefabData = JSON.parse(JSON.stringify(templateData)); prefabData[0]._name = sheetName; prefabData[1]._name = sheetName; applyGridData(prefabData, templateData, grids); } const jsonStr = JSON.stringify(prefabData, null, 2); const url = `db://assets/bundles/battleMap/prefab/地图/${sheetName}.prefab`; await Editor.Message.request('asset-db', 'create-asset', url, jsonStr, { overwrite: true }); const stats: Record = {}; for (const m of grids) { const k = m || '(空白)'; stats[k] = (stats[k] || 0) + 1; } const statStr = Object.entries(stats).sort((a, b) => b[1] - a[1]).map(([k, v]) => `${k}:${v}`).join(', '); console.log(`[地图生成] ${exists ? '~' : '+'} ${sheetName}.prefab (${statStr})`); } console.log('[地图生成] 全部完成!'); } catch (err) { console.error('[地图生成] 错误:', err); } }, }; export function load() { } export function unload() { }