Files
kidgo/js/settings.js
T
2026-10-23 17:49:29 +08:00

179 lines
6.6 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 设置:用户名 + 手机虚拟手柄按钮的自定义(位置/大小,拖动调整)
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 = `<span>${b.label}</span>` + (b.label2 ? `<span class="lbl">${b.label2}</span>` : '');
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);
}