// 设置:用户名 + 手机虚拟手柄按钮的自定义(位置/大小,拖动调整) import { Save } from './save.js'; // 每个可自定义的手柄按钮:key 对应 touch.js 里的 data-touch, // def 为默认布局(cx/cy 为相对舞台中心的百分比,size 为按钮边长 px) const BUTTONS = [ { key: 'left', label: '◀', shape: 'round', def: { cx: 12, cy: 80, size: 62 } }, { key: 'right', label: '▶', shape: 'round', def: { cx: 24, cy: 80, size: 62 } }, { key: 'jump', label: '跳', shape: 'jump', def: { cx: 88, cy: 84, size: 94 } }, { key: 'shoot', label: '🔫', shape: 'item', label2: '射击', def: { cx: 79, cy: 72, size: 54 } }, { key: 'down', label: '▼', shape: 'item', label2: '下蹲', def: { cx: 90, cy: 72, size: 54 } }, { key: 'pause', label: '❚❚', shape: 'sm', def: { cx: 81, cy: 58, size: 42 } }, { key: 'suicide', label: '↺', shape: 'sm', def: { cx: 90, cy: 58, size: 42 } }, ]; const clone = (o) => JSON.parse(JSON.stringify(o)); const DEFAULT_TOUCH = {}; BUTTONS.forEach(b => { DEFAULT_TOUCH[b.key] = clone(b.def); }); const makeDefaults = () => ({ username: '', touch: clone(DEFAULT_TOUCH) }); // 工作副本(未点「确定」前不落盘,点「返回」即丢弃) let work = makeDefaults(); let wrap = null; const els = {}; /** 读取/补全存档里的设置对象(保证 touch 字段齐全) */ function meta() { if (!Save.data.settings) Save.data.settings = { username: '' }; if (!Save.data.settings.touch) Save.data.settings.touch = clone(DEFAULT_TOUCH); return Save.data.settings; } /** 把某个按钮按 p={cx,cy,size} 绝对定位(百分比定位 + 像素边长) */ function place(el, p) { el.style.left = p.cx + '%'; el.style.top = p.cy + '%'; el.style.width = p.size + 'px'; el.style.height = p.size + 'px'; } /** 把当前存档布局应用到真正的 #touch 手柄按钮(游戏内生效) */ function apply() { const t = meta().touch; for (const b of BUTTONS) { const el = document.querySelector(`#touch [data-touch="${b.key}"]`); if (!el) continue; const p = t[b.key]; if (!p) continue; el.style.left = p.cx + '%'; el.style.top = p.cy + '%'; el.style.width = p.size + 'px'; el.style.height = p.size + 'px'; } } /** 标题界面显示当前用户名 */ function showUsername() { const el = document.getElementById('title-user'); if (el) el.textContent = '玩家:' + (meta().username || '无名氏'); } export const Settings = { init() { wrap = document.getElementById('touch-preview'); work = clone(meta()); this.build(); showUsername(); apply(); }, /** 构建设置界面里的可拖拽手柄预览(克隆 #touch 的按钮形状) */ build() { if (!wrap) return; wrap.querySelectorAll('.tc-btn').forEach(n => n.remove()); for (const b of BUTTONS) { const el = document.createElement('div'); el.className = 'tc-btn tc-' + b.shape; el.innerHTML = `${b.label}` + (b.label2 ? `${b.label2}` : ''); const h = document.createElement('div'); h.className = 'tc-rsz'; el.appendChild(h); wrap.appendChild(el); els[b.key] = el; place(el, work.touch[b.key]); wire(el, h, b.key); } }, /** 打开设置界面:从存档同步工作副本与输入框 */ enter() { const s = meta(); work = { username: s.username || '', touch: clone(s.touch) }; const inp = document.getElementById('set-username'); if (inp) inp.value = work.username; this.build(); }, /** 确定:保存用户名与手柄布局到存档 */ confirm() { const s = meta(); const inp = document.getElementById('set-username'); work.username = (inp ? inp.value : '').trim().slice(0, 16); s.username = work.username; s.touch = clone(work.touch); Save.flush(); showUsername(); apply(); }, /** 恢复默认:布局与用户名全部回到出厂值(仅影响工作副本,需点确定才落盘) */ reset() { work = makeDefaults(); const inp = document.getElementById('set-username'); if (inp) inp.value = ''; this.build(); }, /** 供调试 / 测试使用 */ _state() { return work; }, _els() { return els; }, }; /** 给单个预览按钮绑定「拖动移动」与「右下角缩放」 */ function wire(el, handle, key) { let mode = null; // 'move' | 'resize' let rect = null; // 预览区矩形(按下时记录) let lastX = 0, lastY = 0; // 上一帧指针(相对位移用) let startSize = 0, startDist = 1, cx0 = 0, cy0 = 0; // 缩放用:按下时按钮中心与指针距离 // 按下:只记录基准,不重算绝对坐标 —— 避免任何「按下即跳角」 const begin = (e, m) => { e.preventDefault(); try { (m === 'resize' ? handle : el).setPointerCapture(e.pointerId); } catch (_) { } mode = m; rect = wrap.getBoundingClientRect(); const W = rect.width || 1, H = rect.height || 1; const p = work.touch[key]; lastX = e.clientX; lastY = e.clientY; if (m === 'resize') { cx0 = rect.left + p.cx / 100 * W; cy0 = rect.top + p.cy / 100 * H; startSize = p.size; startDist = Math.max(1, Math.hypot(e.clientX - cx0, e.clientY - cy0)); } }; // 移动:用「相对上一帧的位移」累加,第一帧必然停在正确位置 const drag = (e) => { if (!mode || !rect) return; const p = work.touch[key]; if (mode === 'move') { const W = rect.width || 1, H = rect.height || 1; const dx = (e.clientX - lastX) / W * 100; // 像素位移 → 百分比 const dy = (e.clientY - lastY) / H * 100; lastX = e.clientX; lastY = e.clientY; p.cx = Math.max(3, Math.min(97, p.cx + dx)); p.cy = Math.max(3, Math.min(97, p.cy + dy)); } else { const d = Math.max(1, Math.hypot(e.clientX - cx0, e.clientY - cy0)); p.size = Math.max(24, Math.min(160, startSize * (d / startDist))); } place(el, p); }; const end = (e) => { if (mode) { try { (mode === 'resize' ? handle : el).releasePointerCapture(e.pointerId); } catch (_) { } } mode = null; }; el.addEventListener('pointerdown', (e) => { if (e.target === handle) return; begin(e, 'move'); }); handle.addEventListener('pointerdown', (e) => { e.stopPropagation(); begin(e, 'resize'); }); el.addEventListener('pointermove', drag); handle.addEventListener('pointermove', drag); el.addEventListener('pointerup', end); handle.addEventListener('pointerup', end); el.addEventListener('pointercancel', end); handle.addEventListener('pointercancel', end); }