You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

275 lines
13 KiB

3 weeks ago
import path from 'path';
const Fs = require('fs');
// ── 属性映射:标记 → MapGrid 组件全部 8 个属性的完整值 ──
const MARKER_PROPS: Record<string, Record<string, boolean>> = {
'': { _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<string, { x: number; y: number }>();
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<string, number> = {};
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() { }