import { ArrowDirection, GridPoint, LevelPath, ParsedLevel, RawLevelJson, } from "./LevelTypes"; /** * 解析 Cocos JsonAsset 导出的关卡 json(例如 level1.json)。 * * 该 json 的结构大致为一个数组: * [ * 1, 0, 0, * [["cc.JsonAsset", ["_name", "json"], 1]], * [[0, 0, 1, 3]], * [[0, "level_new_1", { ...关卡对象... }]], * 0, 0, [], [], [] * ] * * 实际关卡对象位于 root[5][0][2]。 */ export function parseLevelJson(raw: unknown): ParsedLevel { if (typeof raw !== "object" || raw === null) { throw new Error("Invalid level json: expected object"); } // Some level assets are exported as Cocos JsonAsset array wrappers. // The actual level object is typically located at root[5][0][2]. let target: unknown = raw; if (Array.isArray(target)) { const root = target as unknown[]; const maybe = root[5]; if ( Array.isArray(maybe) && Array.isArray(maybe[0]) && (maybe[0] as unknown[]).length >= 3 ) { target = (maybe[0] as unknown[])[2]; } } if (typeof target !== "object" || target === null) { throw new Error("Invalid level json: expected object"); } const obj = target as Partial; const index: number = typeof obj.name === "number" ? obj.name : 0; const rows: number = typeof obj.row === "number" ? obj.row : 0; const cols: number = typeof obj.col === "number" ? obj.col : 0; const levelTime: number = typeof obj.levelTime === "number" && obj.levelTime > 0 ? obj.levelTime : 0; const rawArrows: unknown = obj.arrows ?? []; if (!Array.isArray(rawArrows)) { throw new Error("Invalid level json: arrows must be an array"); } const paths: LevelPath[] = (rawArrows as unknown[]).map( (rawPath, pathIndex) => { if (!Array.isArray(rawPath)) { throw new Error( `Invalid level json: elements[${pathIndex}] is not an array`, ); } // arrows:第一个点的第三个元素为颜色字符串(可选) const firstPoint = (rawPath as unknown[])[0]; let pathColor: string | undefined; if (Array.isArray(firstPoint) && firstPoint.length >= 3) { const maybeColor = firstPoint[2]; if (typeof maybeColor === "string") { pathColor = maybeColor; } } const points: GridPoint[] = (rawPath as unknown[]).map( (p, pointIndex) => { if (!Array.isArray(p) || p.length < 2) { throw new Error( `Invalid level json: elements[${pathIndex}][${pointIndex}] not [x,y]`, ); } const [x, cocosY] = p as [number, number]; // Cocos 的关卡坐标以底部为 y=0,向上递增; // Phaser 中我们使用传统屏幕坐标:y 从上往下递增。 // 因此在解析阶段进行一次上下翻转,统一成“0 在顶部”的行列索引。 const phaserY = rows > 0 ? rows - 1 - cocosY : cocosY; return { x, y: phaserY }; }, ); if (points.length === 0) { return { points: [], head: { x: 0, y: 0 }, direction: ArrowDirection.Right, indices: [], }; } const head = points[points.length - 1]!; const beforeHead = points[Math.max(0, points.length - 2)]!; const dx = head.x - beforeHead.x; const dy = head.y - beforeHead.y; let direction: ArrowDirection; if (Math.abs(dx) >= Math.abs(dy)) { // 水平为主 direction = dx < 0 ? ArrowDirection.Left : ArrowDirection.Right; } else { // 垂直为主 direction = dy < 0 ? ArrowDirection.Up : ArrowDirection.Down; } // 将路径离散成覆盖到的所有网格索引(含端点),方便构建棋盘格子 const indices: number[] = []; const pushIndex = (x: number, y: number) => { if (x < 0 || y < 0 || x >= cols || y >= rows) return; const idx = y * cols + x; if (!indices.includes(idx)) { indices.push(idx); } }; for (let i = 0; i < points.length - 1; i++) { const a = points[i]!; const b = points[i + 1]!; if (a.x === b.x) { // 垂直线段 const step = b.y > a.y ? 1 : -1; for (let y = a.y; y !== b.y + step; y += step) { pushIndex(a.x, y); } } else if (a.y === b.y) { // 水平线段 const step = b.x > a.x ? 1 : -1; for (let x = a.x; x !== b.x + step; x += step) { pushIndex(x, a.y); } } else { // 理论上不会出现斜线,如果有就只记录端点,避免崩溃 pushIndex(a.x, a.y); pushIndex(b.x, b.y); } } return { points, head, direction, indices, ...(pathColor !== undefined ? { color: pathColor } : {}), }; }, ); const timeSeconds = levelTime; return { index, rows, cols, paths, timeSeconds, ...(obj.background !== undefined ? { background: obj.background } : {}), }; } /** * 将关卡 JSON 解析为 Three.js XZ 平面坐标。 * * 坐标映射关系: * - Phaser GridPoint.x → Three.js position.x(向右) * - Phaser GridPoint.y → Three.js position.z(向下 / 屏幕内) * - Three.js position.y 固定为 0(地面高度) * * 由于 Phaser 的 (x, y_down) 和 Three.js 俯视的 (x, z_down) 语义一致, * 实际上复用 parseLevelJson 即可,无需额外翻转。 */ export function parseLevelFor3D(raw: unknown): ParsedLevel { return parseLevelJson(raw); }