fixed touchscreen controls stuck bug

This commit is contained in:
azykov@mail.ru 2026-06-04 14:55:19 +03:00
parent c80b76e06e
commit 1210201802
No known key found for this signature in database
1 changed files with 66 additions and 33 deletions

View File

@ -1,6 +1,6 @@
import { useEffect, useRef } from "react"; import { useEffect, useRef } from "react";
import { create } from "nipplejs"; import { create } from "nipplejs";
import { DEFAULT_JOYSTICK_VALUES, joystickValues } from "../joystickInput"; import { joystickValues } from "../joystickInput";
// const isTouch = navigator.maxTouchPoints > 0; // const isTouch = navigator.maxTouchPoints > 0;
const isTouch = true; // debug const isTouch = true; // debug
@ -11,7 +11,7 @@ export function JoystickView() {
const lookZoneRef = useRef<HTMLDivElement>(null); const lookZoneRef = useRef<HTMLDivElement>(null);
useEffect(() => { useEffect(() => {
if (!isTouch) if (!isTouch)
return; return;
@ -22,67 +22,100 @@ export function JoystickView() {
if (!moveZone || !jumpBtn || !lookZone) if (!moveZone || !jumpBtn || !lookZone)
return; return;
const moveJoystick = create({ let moveJoystick: ReturnType<typeof create>;
zone: moveZone, let lookJoystick: ReturnType<typeof create>;
mode: 'static',
position: { left: '80px', bottom: '80px' },
color: 'white',
shape: 'square',
size: 100,
});
const lookJoystick = create({ const setup = () => {
zone: lookZone, moveJoystick = create({
mode: 'dynamic', zone: moveZone,
dataOnly: true, mode: 'static',
}); position: { left: '80px', bottom: '80px' },
color: 'white',
shape: 'circle',
size: 100,
});
moveJoystick.on('move', (evt) => { lookJoystick = create({
joystickValues.move.x = evt.data.vector.x; zone: lookZone,
joystickValues.move.y = evt.data.vector.y; mode: 'dynamic',
}); dataOnly: true,
moveJoystick.on('end', () => { });
moveJoystick.on('move', (evt) => {
joystickValues.move.x = evt.data.vector.x;
joystickValues.move.y = evt.data.vector.y;
});
moveJoystick.on('end', () => {
joystickValues.move = { x: 0, y: 0 };
});
lookJoystick.on('move', (evt) => {
joystickValues.look = evt.data.vector;
});
lookJoystick.on('end', () => {
joystickValues.look = { x: 0, y: 0 };
});
};
const teardown = () => {
moveJoystick?.destroy();
lookJoystick?.destroy();
joystickValues.move = { x: 0, y: 0 }; joystickValues.move = { x: 0, y: 0 };
});
lookJoystick.on('move', (evt) => {
joystickValues.look = evt.data.vector;
});
lookJoystick.on('end', () => {
joystickValues.look = { x: 0, y: 0 }; joystickValues.look = { x: 0, y: 0 };
}); joystickValues.jump = false;
};
setup();
const onJumpDown = () => { joystickValues.jump = true; }; const onJumpDown = () => { joystickValues.jump = true; };
const onJumpUp = () => { joystickValues.jump = false; }; const onJumpUp = () => { joystickValues.jump = false; };
jumpBtn.addEventListener('pointerdown', onJumpDown); jumpBtn.addEventListener('pointerdown', onJumpDown);
jumpBtn.addEventListener('pointerup', onJumpUp); jumpBtn.addEventListener('pointerup', onJumpUp);
jumpBtn.addEventListener('pointercancel', onJumpUp);
jumpBtn.addEventListener('pointerleave', onJumpUp); jumpBtn.addEventListener('pointerleave', onJumpUp);
// When the OS interrupts a touch (app switch, notification, etc.) the browser
// fires pointercancel instead of pointerup, leaving nipplejs with a stuck active
// identifier. In dynamic mode this silently drops all subsequent touches on the
// look zone. Destroying and recreating resets nipplejs's internal touch tracking.
const onVisibilityChange = () => {
if (document.hidden) {
teardown();
} else {
setup();
}
};
document.addEventListener('visibilitychange', onVisibilityChange);
return () => { return () => {
moveJoystick.destroy(); teardown();
lookJoystick.destroy();
jumpBtn.removeEventListener('pointerdown', onJumpDown); jumpBtn.removeEventListener('pointerdown', onJumpDown);
jumpBtn.removeEventListener('pointerup', onJumpUp); jumpBtn.removeEventListener('pointerup', onJumpUp);
jumpBtn.removeEventListener('pointercancel', onJumpUp);
jumpBtn.removeEventListener('pointerleave', onJumpUp); jumpBtn.removeEventListener('pointerleave', onJumpUp);
Object.assign(joystickValues, DEFAULT_JOYSTICK_VALUES); document.removeEventListener('visibilitychange', onVisibilityChange);
}; };
}, []); }, []);
if (!isTouch) return null; if (!isTouch)
return null;
return ( return (
<> <>
<div <div
ref={moveZoneRef} ref={moveZoneRef}
style={{ position: 'absolute', left: 0, bottom: 0, width: '50%', height: '45%', touchAction: 'none' }} id="move-zone"
style={{ position: 'absolute', left: 0, bottom: 0, width: '30vw', height: '30vw', touchAction: 'none', background: 'red' }}
/> />
<div <div
ref={lookZoneRef} ref={lookZoneRef}
style={{ position: 'absolute', right: 0, bottom: 0, width: '50%', height: '45%', touchAction: 'none' }} id="look-zone"
style={{ position: 'absolute', right: 0, bottom: 0, width: '100%', height: '100%', touchAction: 'none' }}
/> />
<div <div
ref={jumpBtnRef} ref={jumpBtnRef}
id="jump-zone"
style={{ style={{
position: 'absolute', position: 'absolute',
right: 30, right: 30,