fix: 优化跳跃和下落手感

This commit is contained in:
cancerai committed 2026-10-24 01:54:39 +08:00
1 parent ada64fe4be
commit afe2f64b9b
25 files changed
+1197 -188

No files matched your search

+136
View File
@@ -0,0 +1,136 @@
// 编辑器交互回归:调色板无「查看属性」工具;无论手上拿什么方块(橡皮除外),
// 点击画布上任意方块都选中该格并在属性面板显示其属性(不覆盖已有方块)。
const noop = () => { };
let fail = 0;
const ok = (c, m) => { console.log((c ? ' ✔ ' : ' ✗ ') + m); if (!c) fail++; };
// ---------- 最小假 DOM(addEventListener 收句柄,children 真实入列)----------
const els = new Map();
function mkEl(id = '') {
const e = {
id, tagName: 'DIV', value: '', _html: '', textContent: '',
style: {}, dataset: {}, children: [],
width: 800, height: 608,
classList: {
_s: new Set(),
add(...c) { c.forEach(x => this._s.add(x)); },
remove(...c) { c.forEach(x => this._s.delete(x)); },
toggle(c, on) { on ? this._s.add(c) : this._s.delete(c); },
contains(c) { return this._s.has(c); },
},
_h: {},
addEventListener(t, fn) { (this._h[t] = this._h[t] || []).push(fn); },
removeEventListener: noop,
appendChild(c) { this.children.push(c); return c; },
querySelector: () => mkEl(), querySelectorAll: () => [],
getBoundingClientRect: () => ({ left: 0, top: 0, width: 800, height: 608 }),
getContext: () => ctx2d, focus: noop, click: noop,
preventDefault: noop,
};
Object.defineProperty(e, 'innerHTML', {
get() { return this._html; },
set(v) { this._html = String(v); this.children = []; },
});
return e;
}
const ctx2d = new Proxy({
canvas: { width: 800, height: 608 },
measureText: () => ({ width: 12 }),
createLinearGradient: () => ({ addColorStop: noop }),
createRadialGradient: () => ({ addColorStop: noop }),
getImageData: () => ({ data: [] }),
}, {
get(t, k) { return k in t ? t[k] : noop; },
set(t, k, v) { t[k] = v; return true; },
});
const getEl = s => { if (!els.has(s)) els.set(s, mkEl(s)); return els.get(s); };
globalThis.location = { search: '', href: 'http://localhost/', pathname: '/', reload: noop };
Object.defineProperty(globalThis, 'navigator', { value: { maxTouchPoints: 0, userAgent: 'node' }, configurable: true });
globalThis.matchMedia = q => ({ matches: false, media: q, addEventListener: noop, removeEventListener: noop });
globalThis.window = {
addEventListener: noop, removeEventListener: noop, AudioContext: null, webkitAudioContext: null,
innerWidth: 1280, innerHeight: 800, location: globalThis.location, navigator: globalThis.navigator, matchMedia: globalThis.matchMedia,
};
globalThis.addEventListener = noop;
globalThis.removeEventListener = noop;
globalThis.confirm = () => true;
globalThis.alert = noop;
globalThis.localStorage = { _d: {}, getItem(k) { return this._d[k] ?? null; }, setItem(k, v) { this._d[k] = String(v); }, removeItem(k) { delete this._d[k]; } };
globalThis.document = {
getElementById: getEl, querySelector: getEl, querySelectorAll: () => [],
createElement(t) { const e = mkEl(); e.tagName = t.toUpperCase(); return e; },
addEventListener: noop, body: mkEl('body'),
};
const { Editor, PALETTE } = await import('../js/editor.js');
// 画布坐标 → 格子:pos() 用 clientX/Y(scroll=0,缩放 1:1),格心 (tx*32+16, ty*32+16)
const ev = (tx, ty, button = 0) => ({ clientX: tx * 32 + 16, clientY: ty * 32 + 16, button, preventDefault: noop });
const clickTile = (tx, ty, button = 0) => {
for (const fn of getEl('#ed-canvas')._h.mousedown) fn(ev(tx, ty, button));
};
// renderProps 先写面板 innerHTML(标题),再把每个属性字段 appendChild 成子行
const propsHtml = () => {
const box = getEl('#ed-props');
return box.innerHTML + box.children.map(c => c.innerHTML || '').join('');
};
console.log('=== 调色板不再有「查看属性」工具 ===');
ok(!PALETTE.some(p => p[0] === '?'), "PALETTE 中已无 '?'(查看属性)");
ok(!PALETTE.some(p => p[1] === '查看属性'), 'PALETTE 中已无「查看属性」条目');
Editor.init({ startLevel: noop });
const palLabels = getEl('#palette').children.map(c => c.textContent || '');
ok(palLabels.length === PALETTE.length, `调色板渲染 ${PALETTE.length} 项(无工具项),实际 ${palLabels.length}`);
ok(!palLabels.some(t => t.includes('查看属性')), '调色板 UI 无「查看属性」按钮');
console.log('=== 打开地图,构造一块带属性的摄像头 C ===');
Editor.open();
const f = Editor.curFace();
f.grid[5][7] = 'C'; // 摄像头:SCHEMA 里有 4 个可编辑属性
f.props[5][7] = { w: 4, h: 4, mode: 'zoom', zoom: 1.5 };
console.log('=== 手上拿刺(^),点击已有摄像头 → 只看属性,不覆盖 ===');
Editor.sel = '^';
clickTile(7, 5);
ok(f.grid[5][7] === 'C', '格子内容未被覆盖(仍是 C)');
ok(Editor.selTile && Editor.selTile.tx === 7 && Editor.selTile.ty === 5, 'selTile 指向被点方块 (7,5)');
const html1 = propsHtml();
ok(html1.includes('区域宽') && html1.includes('镜头模式'), '属性面板显示该方块的属性(区域宽/镜头模式)');
console.log('=== 手上拿方块(#),点击同一格 → 同样只查看,不覆盖 ===');
Editor.sel = '#';
clickTile(7, 5);
ok(f.grid[5][7] === 'C', '拿 # 点击也不覆盖已有方块');
ok(propsHtml().includes('区域宽'), '属性面板仍显示 C 的属性');
console.log('=== 手上拿炮台(O),点击空格子 → 放置并显示新方块属性 ===');
Editor.sel = 'O';
clickTile(3, 9);
ok(f.grid[9][3] === 'O', '空格子正常放置 O');
ok(propsHtml().includes('发射间隔'), '放置后属性面板显示 O 的属性');
console.log('=== 提示牌 T 的文本属性可随点击查看 ===');
f.grid[9][4] = 'T'; f.props[9][4] = { msg: '小心刺' };
Editor.sel = '%'; // 拿着冰面点提示牌
clickTile(4, 9);
ok(f.grid[9][4] === 'T' && propsHtml().includes('提示文字'), '非相关工具点击 T 也能查看文字属性');
console.log('=== 橡皮擦例外:选中橡皮点击 → 擦除 ===');
Editor.sel = '.';
clickTile(4, 9);
ok(f.grid[9][4] === '.', '橡皮擦点击后 T 被擦除');
Editor.sel = '^'; // 恢复非橡皮工具
clickTile(7, 5); // 顺手确认 C 还在
console.log('=== 右键擦除与工具无关 ===');
Editor.sel = '#';
clickTile(3, 9, 2); // button=2 右键
ok(f.grid[9][3] === '.', '右键擦除不受手上工具影响');
console.log('=== 属性面板空态提示仍存在 ===');
Editor.show(); // 重置 selTile
ok(propsHtml().includes('点击画布上的方块'), '未选中时显示引导文案');
console.log(fail === 0 ? '\n✅ 编辑器点击查看属性 全部断言通过' : `\n❌ 失败 ${fail} 项`);
process.exit(fail === 0 ? 0 : 1);