99 lines
2.9 KiB
JavaScript
99 lines
2.9 KiB
JavaScript
// 移动端虚拟手柄:左手方向、右手跳跃 + 道具(参考 IWBT Creator 的触屏布局)
|
|
// 左下方:← → 移动;右下方:大跳键 + 射击/下蹲 道具键 + 暂停/重生
|
|
import { setTouch, clearTouch } from './input.js';
|
|
import { Settings } from './settings.js';
|
|
|
|
// 持续按住型动作(通过 action 状态机驱动)
|
|
const HELD = ['left', 'right', 'jump', 'down'];
|
|
|
|
const api = {
|
|
pause() {},
|
|
suicide() {},
|
|
shoot() {},
|
|
};
|
|
|
|
let visible = false;
|
|
let root = null;
|
|
|
|
function q(sel) { return document.querySelector(sel); }
|
|
|
|
function bind(btn) {
|
|
const action = btn.dataset.touch;
|
|
if (!action) return;
|
|
let timer = null;
|
|
|
|
const press = (e) => {
|
|
e.preventDefault();
|
|
if (btn._pid != null) return; // 已在按住(多指防重入)
|
|
btn._pid = e.pointerId;
|
|
try { btn.setPointerCapture(e.pointerId); } catch (_) {}
|
|
btn.classList.add('on');
|
|
|
|
if (action === 'shoot') {
|
|
// 长按连发(受玩家冷却与是否持枪限制)
|
|
api.shoot();
|
|
timer = setInterval(() => api.shoot(), 140);
|
|
} else if (HELD.includes(action)) {
|
|
setTouch(action, true);
|
|
} else if (action === 'pause') {
|
|
api.pause();
|
|
} else if (action === 'suicide') {
|
|
api.suicide();
|
|
}
|
|
};
|
|
|
|
const release = (e) => {
|
|
if (btn._pid != null && e.pointerId !== btn._pid) return;
|
|
btn._pid = null;
|
|
try { btn.releasePointerCapture(e.pointerId); } catch (_) {}
|
|
btn.classList.remove('on');
|
|
if (action === 'shoot') {
|
|
if (timer) { clearInterval(timer); timer = null; }
|
|
} else if (HELD.includes(action)) {
|
|
setTouch(action, false);
|
|
}
|
|
};
|
|
|
|
btn.addEventListener('pointerdown', press);
|
|
btn.addEventListener('pointerup', release);
|
|
btn.addEventListener('pointercancel', release);
|
|
btn.addEventListener('lostpointercapture', release);
|
|
btn.addEventListener('contextmenu', (e) => e.preventDefault());
|
|
}
|
|
|
|
export const Touch = {
|
|
init(handlers) {
|
|
Object.assign(api, handlers || {});
|
|
root = q('#touch');
|
|
root.querySelectorAll('[data-touch]').forEach(bind);
|
|
// 游戏进行中禁止页面滚动/回弹
|
|
const stage = q('#stage');
|
|
if (stage) {
|
|
stage.addEventListener('touchmove', (e) => { if (visible) e.preventDefault(); }, { passive: false });
|
|
stage.addEventListener('contextmenu', (e) => { if (visible) e.preventDefault(); });
|
|
}
|
|
},
|
|
|
|
show() {
|
|
if (!root) root = q('#touch');
|
|
if (!root) return;
|
|
visible = true;
|
|
Settings.apply(); // 应用用户自定义的手柄布局
|
|
root.classList.remove('hidden');
|
|
},
|
|
|
|
hide() {
|
|
if (!root) root = q('#touch');
|
|
if (!root) return;
|
|
visible = false;
|
|
root.classList.add('hidden');
|
|
clearTouch(); // 防止按键卡住
|
|
},
|
|
|
|
/** 反映是否持有手枪:无枪时射击键置灰 */
|
|
setGun(has) {
|
|
const g = q('#tc-gun');
|
|
if (g) g.classList.toggle('off', !has);
|
|
},
|
|
};
|