Files
cocos/extensions/level-map-editor/dist/editor-tools.js
2026-07-14 16:02:20 +08:00

200 lines
5.8 KiB
JavaScript

'use strict';
const TOOLS = ['paint', 'box', 'picker', 'eraser', 'fill'];
const TOOL_LABELS = {
paint: '画笔:在网格上点击或拖拽绘制(需先在右侧选瓦片)',
box: '框选:拖拽矩形区域,松开后用当前瓦片填满',
picker: '吸管:点击已有格子,吸取瓦片为当前画笔',
eraser: '橡皮擦:点击或拖拽删除当前层的格子',
fill: '油漆桶:填充相连的同类型格子(需先选瓦片)',
};
const TOOL_CURSORS = {
paint: 'crosshair',
box: 'crosshair',
picker: 'copy',
eraser: 'not-allowed',
fill: 'cell',
};
function layerMap(state) {
return state.editLayer === 'ground' ? state.config.ground : state.config.border;
}
function getCell(state, key) {
const map = layerMap(state);
return map[key];
}
function hasCell(state, key) {
return getCell(state, key) !== undefined;
}
function removeCell(state, key) {
if (state.editLayer === 'ground') delete state.config.ground[key];
else delete state.config.border[key];
}
function setCell(state, key, tileKey) {
if (state.editLayer === 'ground') {
state.config.ground[key] = tileKey === 'JumpBlock' ? 'JumpBlock' : 'Baseblock';
} else {
const s = typeof tileKey === 'string' ? tileKey.trim() : '';
state.config.border[key] = (!s || s === 'Baseblock' || s === 'JumpBlock') ? 'WallBlock' : s;
}
}
function canPaintBrush(state) {
return state.brush && state.brush.layer === state.editLayer;
}
function fillRectangle(state, x0, y0, x1, y1, tileKey) {
const minX = Math.min(x0, x1);
const maxX = Math.max(x0, x1);
const minY = Math.min(y0, y1);
const maxY = Math.max(y0, y1);
let n = 0;
for (let x = minX; x <= maxX; x++) {
for (let y = minY; y <= maxY; y++) {
setCell(state, `${x},${y}`, tileKey);
n++;
}
}
return n;
}
function eraseRectangle(state, x0, y0, x1, y1) {
const minX = Math.min(x0, x1);
const maxX = Math.max(x0, x1);
const minY = Math.min(y0, y1);
const maxY = Math.max(y0, y1);
let n = 0;
for (let x = minX; x <= maxX; x++) {
for (let y = minY; y <= maxY; y++) {
const key = `${x},${y}`;
if (hasCell(state, key)) {
removeCell(state, key);
n++;
}
}
}
return n;
}
/** 四连通洪水填充(仅当前编辑层) */
function floodFill(state, startKey, fillTileKey) {
const map = layerMap(state);
const target = map[startKey];
if (target === fillTileKey) return 0;
const q = [startKey];
const seen = new Set([startKey]);
let n = 0;
while (q.length) {
const key = q.shift();
map[key] = fillTileKey;
n++;
const [xs, ys] = key.split(',');
const x = parseInt(xs, 10);
const y = parseInt(ys, 10);
for (const [nx, ny] of [[x + 1, y], [x - 1, y], [x, y + 1], [x, y - 1]]) {
const nk = `${nx},${ny}`;
if (seen.has(nk)) continue;
const v = map[nk];
const same = target === undefined ? v === undefined : v === target;
if (!same) continue;
seen.add(nk);
q.push(nk);
}
}
return n;
}
/** 洪水清除(橡皮擦油漆桶:删相连同值区域) */
function floodErase(state, startKey) {
const map = layerMap(state);
if (map[startKey] === undefined) return 0;
const target = map[startKey];
const q = [startKey];
const seen = new Set([startKey]);
let n = 0;
while (q.length) {
const key = q.shift();
delete map[key];
n++;
const [xs, ys] = key.split(',');
const x = parseInt(xs, 10);
const y = parseInt(ys, 10);
for (const [nx, ny] of [[x + 1, y], [x - 1, y], [x, y + 1], [x, y - 1]]) {
const nk = `${nx},${ny}`;
if (seen.has(nk) || map[nk] !== target) continue;
seen.add(nk);
q.push(nk);
}
}
return n;
}
function findPaletteTile(state, tileKey, layer) {
return state.tiles.find((t) => t.tileKey === tileKey && t.layer === layer) || null;
}
/** Ground 瓦片中文名 */
function groundTileLabel(tileKey) {
if (tileKey === 'JumpBlock') return '跳跃砖(JumpBlock)';
return '可移动砖块(Baseblock)';
}
/** Border 瓦片中文名 */
function borderTileLabel(tileKey) {
const s = tileKey === true || tileKey === 1 ? 'WallBlock' : String(tileKey || 'WallBlock').trim();
if (!s || s === 'WallBlock') return '墙砖(WallBlock)';
return `墙饰(${s})`;
}
/**
* 汇总格子上全部内容(两层瓦片 + 实体),用于状态栏悬停提示。
* @param {object} state
* @param {number} x
* @param {number} y
* @param {{ kind: string, propPlacement?: string }[]} [spawnHits]
* @param {Record<string, string>} [spawnKindLabels]
*/
function describeCellContents(state, x, y, spawnHits, spawnKindLabels) {
const key = `${x},${y}`;
const parts = [];
const ground = state.config?.ground?.[key];
if (ground !== undefined) parts.push(groundTileLabel(ground));
const border = state.config?.border?.[key];
if (border !== undefined) parts.push(borderTileLabel(border));
if (Array.isArray(spawnHits)) {
for (const h of spawnHits) {
let label = (spawnKindLabels && spawnKindLabels[h.kind]) || h.kind;
if (h.kind === 'player') label = '人物';
if (h.kind === 'prop' && h.propPlacement === 'ground') label = `${label}(空地)`;
parts.push(label);
}
}
return parts.length ? parts.join(' + ') : '空';
}
module.exports = {
TOOLS,
TOOL_LABELS,
TOOL_CURSORS,
layerMap,
getCell,
hasCell,
removeCell,
setCell,
canPaintBrush,
fillRectangle,
eraseRectangle,
floodFill,
floodErase,
findPaletteTile,
groundTileLabel,
borderTileLabel,
describeCellContents,
};