chore(controller): three-player-controller als Ordner statt Submodul
Das Submodul zeigte auf GitHub, der eingetragene Stand 028c652 lag aber nur lokal vor: GitHub-Master ac39682 plus Showcase mit Figuren und launch.json. Frische Klone konnten den Controller deshalb nicht laden. Jetzt liegt dieser Stand als normaler Ordner im Repo, ohne Verweis auf GitHub. dist/ und node_modules/ im Controller bleiben ignoriert und werden wie bisher lokal gebaut. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
780e246fe5
commit
fb38652e7f
153 files changed
+42561
-4
No files matched your search
@@ -0,0 +1,173 @@
|
||||
// 按键文字 -> KeyboardEvent.code(可被 data-code 覆盖)
|
||||
const LABEL_CODES = {
|
||||
W: ["KeyW", "ArrowUp"],
|
||||
A: ["KeyA", "ArrowLeft"],
|
||||
S: ["KeyS", "ArrowDown"],
|
||||
D: ["KeyD", "ArrowRight"],
|
||||
Shift: ["ShiftLeft", "ShiftRight"],
|
||||
Space: ["Space"],
|
||||
RMB: ["Mouse2"],
|
||||
LMB: ["Mouse0"],
|
||||
1: ["Digit1"],
|
||||
4: ["Digit4"],
|
||||
};
|
||||
|
||||
// 在 GUI / 输入框里操作时不点亮 HUD
|
||||
function shouldIgnore(el) {
|
||||
if (!(el instanceof Element)) return false;
|
||||
return Boolean(el.closest("input, textarea, select, [contenteditable='true'], .lil-gui"));
|
||||
}
|
||||
|
||||
// 解析 kbd 对应的键码:优先 data-code,否则按显示文字查表
|
||||
function codesFor(kbd) {
|
||||
const explicit = kbd.getAttribute("data-code");
|
||||
if (explicit) return explicit.trim().split(/\s+/).filter(Boolean);
|
||||
const label = kbd.textContent.replace(/\s+/g, " ").trim();
|
||||
if (LABEL_CODES[label]) return LABEL_CODES[label];
|
||||
if (/^[A-Za-z]$/.test(label)) return [`Key${label.toUpperCase()}`];
|
||||
return [];
|
||||
}
|
||||
|
||||
const LOOK_SCALE = 0.28; // 鼠标位移换算到指示点的系数
|
||||
const LOOK_MAX = 5; // 指示点最大偏移(px)
|
||||
const LOOK_DECAY = 0.84; // 每帧回弹衰减
|
||||
const LOOK_REST = 0.04; // 小于此值视为静止
|
||||
|
||||
// 确保每个移动提示块都有鼠标图标(HTML 未写时补一个)
|
||||
function ensureLookMice(hud) {
|
||||
const mice = [];
|
||||
for (const move of hud.querySelectorAll(".hint-move")) {
|
||||
let mouse = move.querySelector(".hint-mouse");
|
||||
if (!mouse) {
|
||||
const cluster = document.createElement("div");
|
||||
cluster.className = "hint-cluster";
|
||||
cluster.innerHTML = '<div class="hint-mouse" aria-hidden="true"><div class="hint-mouse-body"><span class="hint-mouse-wheel"></span><span class="hint-mouse-nub"></span></div></div><span class="hint-text">Look</span>';
|
||||
move.append(cluster);
|
||||
mouse = cluster.querySelector(".hint-mouse");
|
||||
}
|
||||
mice.push(mouse);
|
||||
}
|
||||
return mice;
|
||||
}
|
||||
|
||||
// 绑定 HUD 按键高亮与鼠标 Look 指示
|
||||
export function bindControlHints(hud = document.querySelector(".hud")) {
|
||||
if (!hud) return;
|
||||
|
||||
const keys = [...hud.querySelectorAll("kbd")];
|
||||
const mice = ensureLookMice(hud);
|
||||
if (!keys.length && !mice.length) return;
|
||||
|
||||
const pressed = new Set(); // 当前按下的 KeyboardEvent.code / MouseN
|
||||
let lookX = 0;
|
||||
let lookY = 0;
|
||||
let lookRaf = 0;
|
||||
|
||||
// 同步 kbd.is-down
|
||||
const syncKeys = () => {
|
||||
for (const kbd of keys) {
|
||||
kbd.classList.toggle("is-down", codesFor(kbd).some((code) => pressed.has(code)));
|
||||
}
|
||||
};
|
||||
|
||||
// 把 Look 偏移写到鼠标图标
|
||||
const applyLook = () => {
|
||||
const x = `${lookX}px`;
|
||||
const y = `${lookY}px`;
|
||||
const active = Math.hypot(lookX, lookY) > LOOK_REST;
|
||||
for (const mouse of mice) {
|
||||
mouse.style.setProperty("--look-x", x);
|
||||
mouse.style.setProperty("--look-y", y);
|
||||
mouse.classList.toggle("is-active", active);
|
||||
}
|
||||
};
|
||||
|
||||
// 指示点回弹;静止后停掉 rAF
|
||||
const tickLook = () => {
|
||||
lookX *= LOOK_DECAY;
|
||||
lookY *= LOOK_DECAY;
|
||||
if (Math.abs(lookX) < LOOK_REST && Math.abs(lookY) < LOOK_REST) {
|
||||
lookX = 0;
|
||||
lookY = 0;
|
||||
lookRaf = 0;
|
||||
applyLook();
|
||||
return;
|
||||
}
|
||||
applyLook();
|
||||
lookRaf = requestAnimationFrame(tickLook);
|
||||
};
|
||||
|
||||
// 键盘按下 / 抬起
|
||||
const onKey = (event, down) => {
|
||||
if (down && shouldIgnore(event.target)) return;
|
||||
if (down) pressed.add(event.code);
|
||||
else pressed.delete(event.code);
|
||||
syncKeys();
|
||||
};
|
||||
|
||||
// 鼠标按键(LMB / RMB 等)
|
||||
const onMouse = (event, down) => {
|
||||
if (down && shouldIgnore(event.target)) return;
|
||||
const code = `Mouse${event.button}`;
|
||||
if (down) pressed.add(code);
|
||||
else pressed.delete(code);
|
||||
syncKeys();
|
||||
};
|
||||
|
||||
// 指针锁定时始终跟踪;否则仅在按住拖拽转视角时跟踪
|
||||
const onMouseMove = (event) => {
|
||||
if (!document.pointerLockElement && event.buttons === 0) return;
|
||||
if (!document.pointerLockElement && shouldIgnore(event.target)) return;
|
||||
lookX = Math.max(-LOOK_MAX, Math.min(LOOK_MAX, lookX + event.movementX * LOOK_SCALE));
|
||||
lookY = Math.max(-LOOK_MAX, Math.min(LOOK_MAX, lookY + event.movementY * LOOK_SCALE));
|
||||
applyLook();
|
||||
if (!lookRaf) lookRaf = requestAnimationFrame(tickLook);
|
||||
};
|
||||
|
||||
// 失焦时清掉按下态和 Look 偏移
|
||||
const clear = () => {
|
||||
pressed.clear();
|
||||
syncKeys();
|
||||
lookX = 0;
|
||||
lookY = 0;
|
||||
if (lookRaf) {
|
||||
cancelAnimationFrame(lookRaf);
|
||||
lookRaf = 0;
|
||||
}
|
||||
applyLook();
|
||||
};
|
||||
|
||||
window.addEventListener("keydown", (event) => onKey(event, true));
|
||||
window.addEventListener("keyup", (event) => onKey(event, false));
|
||||
window.addEventListener("mousedown", (event) => onMouse(event, true));
|
||||
window.addEventListener("mouseup", (event) => onMouse(event, false));
|
||||
window.addEventListener("mousemove", onMouseMove);
|
||||
window.addEventListener("blur", clear);
|
||||
document.addEventListener("visibilitychange", () => {
|
||||
if (document.hidden) clear();
|
||||
});
|
||||
}
|
||||
|
||||
// 按上下车切换走路 / 载具两套提示,并转调原有回调
|
||||
export function bindVehicleHintMode(player, hud = document.querySelector(".hud")) {
|
||||
if (!player || !hud) return;
|
||||
|
||||
const setMode = (mode) => {
|
||||
hud.dataset.hintMode = mode;
|
||||
};
|
||||
|
||||
setMode(player.getControllerMode?.() === 1 ? "vehicle" : "on-foot");
|
||||
|
||||
const prevEnter = player.onVehicleEnter;
|
||||
const prevExit = player.onVehicleExit;
|
||||
player.onVehicleEnter = (vehicle) => {
|
||||
setMode("vehicle");
|
||||
prevEnter?.(vehicle);
|
||||
};
|
||||
player.onVehicleExit = (vehicle) => {
|
||||
setMode("on-foot");
|
||||
prevExit?.(vehicle);
|
||||
};
|
||||
}
|
||||
|
||||
bindControlHints();
|
||||
Reference in new issue
Block a user