Skip to content

Commit 5eda6cc

Browse files
committed
Add basic gamepad / Quest controller virtual cursor support for BallArena
1 parent c689d6c commit 5eda6cc

1 file changed

Lines changed: 70 additions & 0 deletions

File tree

projects/BallArena/main.js

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3408,6 +3408,76 @@ window.toggleNarratorSetting = toggleNarratorSetting;
34083408
window.toggleMusicSetting = toggleMusicSetting;
34093409
window.toggleSFXSetting = toggleSFXSetting;
34103410
window.toggleShakeSetting = toggleShakeSetting;
3411+
3412+
// --- Gamepad / Quest Controller Support ---
3413+
// Adds a simple virtual cursor controlled by gamepad axes and maps the primary
3414+
// button to mouse/pointer events so the Quest browser's controller can interact
3415+
// with the game's UI and canvas.
3416+
(() => {
3417+
let gpIndex = null;
3418+
let lastPrimary = false;
3419+
const cursor = { x: window.innerWidth / 2, y: window.innerHeight / 2 };
3420+
3421+
const el = document.createElement('div');
3422+
el.id = 'gp-cursor';
3423+
el.style.cssText = 'position:fixed;left:0;top:0;width:16px;height:16px;background:rgba(255,255,255,0.9);border-radius:50%;box-shadow:0 0 12px rgba(0,200,255,0.9);transform:translate(-50%,-50%);pointer-events:none;z-index:99999;display:none;';
3424+
document.body.appendChild(el);
3425+
3426+
window.addEventListener('gamepadconnected', (e) => {
3427+
gpIndex = e.gamepad.index;
3428+
el.style.display = 'block';
3429+
console.log('[Gamepad] connected', e.gamepad.id, 'index', gpIndex);
3430+
});
3431+
window.addEventListener('gamepaddisconnected', (e) => {
3432+
if (gpIndex === e.gamepad.index) gpIndex = null;
3433+
el.style.display = 'none';
3434+
console.log('[Gamepad] disconnected', e.gamepad.id);
3435+
});
3436+
3437+
function simulateMouse(type, x, y) {
3438+
const target = document.elementFromPoint(x, y);
3439+
if (!target) return;
3440+
const ev = new MouseEvent(type, {
3441+
view: window,
3442+
bubbles: true,
3443+
cancelable: true,
3444+
clientX: Math.round(x),
3445+
clientY: Math.round(y)
3446+
});
3447+
target.dispatchEvent(ev);
3448+
}
3449+
3450+
function poll() {
3451+
const gps = navigator.getGamepads && navigator.getGamepads();
3452+
const gp = (gpIndex !== null && gps && gps[gpIndex]) ? gps[gpIndex] : (gps && gps[0]);
3453+
if (gp) {
3454+
// Typical mapping: axes[0]/[1] = left stick
3455+
const lx = gp.axes[0] || 0;
3456+
const ly = gp.axes[1] || 0;
3457+
const sensitivity = Math.max(window.innerWidth, window.innerHeight) * 0.02; // screen scaled speed
3458+
cursor.x = Math.min(window.innerWidth, Math.max(0, cursor.x + lx * sensitivity));
3459+
cursor.y = Math.min(window.innerHeight, Math.max(0, cursor.y + ly * sensitivity));
3460+
el.style.left = cursor.x + 'px';
3461+
el.style.top = cursor.y + 'px';
3462+
3463+
// Primary button (A) usually index 0; treat pressed->mousedown, release->mouseup+click
3464+
const primary = !!(gp.buttons && gp.buttons[0] && gp.buttons[0].pressed);
3465+
if (primary && !lastPrimary) {
3466+
simulateMouse('pointerdown', cursor.x, cursor.y);
3467+
simulateMouse('mousedown', cursor.x, cursor.y);
3468+
} else if (!primary && lastPrimary) {
3469+
simulateMouse('pointerup', cursor.x, cursor.y);
3470+
simulateMouse('mouseup', cursor.x, cursor.y);
3471+
simulateMouse('click', cursor.x, cursor.y);
3472+
}
3473+
lastPrimary = primary;
3474+
}
3475+
requestAnimationFrame(poll);
3476+
}
3477+
3478+
// Start polling loop (will be inert until a gamepad appears)
3479+
requestAnimationFrame(poll);
3480+
})();
34113481
window.toggleSlowMoSetting = toggleSlowMoSetting;
34123482
window.closeSettings = closeSettings;
34133483
window.initMode = initMode;

0 commit comments

Comments
 (0)