mirror of
https://github.com/sndrec/WebMonkeyBall.git
synced 2026-02-03 10:13:33 +00:00
gyro A press input, other stuff
This commit is contained in:
@@ -201,6 +201,10 @@
|
||||
Level Select
|
||||
</button>
|
||||
|
||||
<button id="fullscreen-button" class="ghost compact fullscreen-button hidden" type="button">
|
||||
Fullscreen
|
||||
</button>
|
||||
|
||||
<div id="gamepad-calibration" class="modal hidden" role="dialog" aria-modal="true" aria-hidden="true">
|
||||
<div class="modal-card">
|
||||
<h2>Stick Calibration</h2>
|
||||
|
||||
@@ -1734,6 +1734,7 @@ export class Game {
|
||||
&& !timeoverActive;
|
||||
const switchesEnabled = this.goalTimerFrames <= 0 && !ringoutActive && !timeoverActive;
|
||||
const isBonusStage = this.isBonusStageActive();
|
||||
this.input?.setGyroTapMode?.(inputEnabled ? 'recalibrate' : 'action');
|
||||
const fastForwardIntro = this.stageAttempts === 1
|
||||
&& this.introTimerFrames > 120
|
||||
&& this.input?.isPrimaryActionDown?.();
|
||||
|
||||
38
src/input.ts
38
src/input.ts
@@ -39,6 +39,7 @@ export class Input {
|
||||
this.inputFalloff = 1;
|
||||
this.touchPreview = false;
|
||||
this.padGate = loadPadGate() ?? DEFAULT_STICK_GATE.map((point) => [point[0], point[1]]);
|
||||
this.gyroTapMode = 'recalibrate';
|
||||
|
||||
this.touchRoot = document.getElementById('touch-controls');
|
||||
this.joystickEl = this.touchRoot?.querySelector?.('.joystick') ?? null;
|
||||
@@ -157,7 +158,7 @@ export class Input {
|
||||
if (this.isOverlayVisible()) {
|
||||
return;
|
||||
}
|
||||
if (this.getControlMode() === 'gyro') {
|
||||
if (this.getControlMode() === 'gyro' && this.gyroTapMode === 'recalibrate') {
|
||||
event.preventDefault();
|
||||
this.recalibrateGyro();
|
||||
return;
|
||||
@@ -184,8 +185,9 @@ export class Input {
|
||||
return;
|
||||
}
|
||||
|
||||
const beta = typeof event.beta === 'number' ? event.beta : 0;
|
||||
const gamma = typeof event.gamma === 'number' ? event.gamma : 0;
|
||||
const rawBeta = typeof event.beta === 'number' ? event.beta : 0;
|
||||
const rawGamma = typeof event.gamma === 'number' ? event.gamma : 0;
|
||||
const { beta, gamma } = adjustGyroForOrientation(rawBeta, rawGamma);
|
||||
this.gyroRaw.beta = beta;
|
||||
this.gyroRaw.gamma = gamma;
|
||||
this.gyroRaw.hasSample = true;
|
||||
@@ -329,6 +331,9 @@ export class Input {
|
||||
if (!this.joystickHandleEl) {
|
||||
return;
|
||||
}
|
||||
if (this.joystickEl?.classList.contains('hidden') && !this.isOverlayVisible()) {
|
||||
this.joystickEl.classList.remove('hidden');
|
||||
}
|
||||
const scale = this.joystickScale || 1;
|
||||
const adjX = dx / scale;
|
||||
const adjY = dy / scale;
|
||||
@@ -400,6 +405,10 @@ export class Input {
|
||||
this.inputFalloff = clamp(value, 1, 2);
|
||||
}
|
||||
|
||||
setGyroTapMode(mode) {
|
||||
this.gyroTapMode = mode === 'action' ? 'action' : 'recalibrate';
|
||||
}
|
||||
|
||||
setPadGate(points) {
|
||||
if (!Array.isArray(points) || points.length !== 8) {
|
||||
return;
|
||||
@@ -730,3 +739,26 @@ function applyInputFalloff(stick, power) {
|
||||
const scale = eased / maxAxis;
|
||||
return { x: stick.x * scale, y: stick.y * scale };
|
||||
}
|
||||
|
||||
function adjustGyroForOrientation(beta, gamma) {
|
||||
const angle = getScreenOrientationAngle();
|
||||
switch (angle) {
|
||||
case 90:
|
||||
return { beta: -gamma, gamma: beta };
|
||||
case 180:
|
||||
return { beta: -beta, gamma: -gamma };
|
||||
case 270:
|
||||
return { beta: gamma, gamma: -beta };
|
||||
default:
|
||||
return { beta, gamma };
|
||||
}
|
||||
}
|
||||
|
||||
function getScreenOrientationAngle() {
|
||||
const orientation = window.screen?.orientation?.angle;
|
||||
if (typeof orientation === 'number') {
|
||||
return ((orientation % 360) + 360) % 360;
|
||||
}
|
||||
const legacy = typeof window.orientation === 'number' ? window.orientation : 0;
|
||||
return ((legacy % 360) + 360) % 360;
|
||||
}
|
||||
|
||||
65
src/main.ts
65
src/main.ts
@@ -67,8 +67,10 @@ function isNaomiStage(stageId: number): boolean {
|
||||
const canvas = document.getElementById('game') as HTMLCanvasElement;
|
||||
const hudCanvas = document.getElementById('hud-canvas') as HTMLCanvasElement;
|
||||
const overlay = document.getElementById('overlay') as HTMLElement;
|
||||
const overlayPanel = document.querySelector('.panel') as HTMLElement | null;
|
||||
const stageFade = document.getElementById('stage-fade') as HTMLElement;
|
||||
const mobileMenuButton = document.getElementById('mobile-menu-button') as HTMLButtonElement | null;
|
||||
const fullscreenButton = document.getElementById('fullscreen-button') as HTMLButtonElement | null;
|
||||
const controlModeField = document.getElementById('control-mode-field') as HTMLElement | null;
|
||||
const controlModeSelect = document.getElementById('control-mode') as HTMLSelectElement | null;
|
||||
const gyroRecalibrateButton = document.getElementById('gyro-recalibrate') as HTMLButtonElement | null;
|
||||
@@ -127,11 +129,29 @@ function updateMobileMenuButtonVisibility() {
|
||||
mobileMenuButton.classList.toggle('hidden', !shouldShow);
|
||||
}
|
||||
|
||||
function updateFullscreenButtonVisibility() {
|
||||
if (!fullscreenButton) {
|
||||
return;
|
||||
}
|
||||
const root = document.documentElement as HTMLElement & {
|
||||
webkitRequestFullscreen?: () => Promise<void> | void;
|
||||
};
|
||||
const supportsFullscreen = typeof root.requestFullscreen === 'function' || typeof root.webkitRequestFullscreen === 'function';
|
||||
const isFullscreen = !!(document.fullscreenElement || (document as typeof document & { webkitFullscreenElement?: Element | null }).webkitFullscreenElement);
|
||||
const shouldShow = hasTouch && supportsFullscreen;
|
||||
fullscreenButton.classList.toggle('hidden', !shouldShow);
|
||||
if (!shouldShow) {
|
||||
return;
|
||||
}
|
||||
fullscreenButton.textContent = supportsFullscreen && isFullscreen ? 'Exit Fullscreen' : 'Fullscreen';
|
||||
}
|
||||
|
||||
function setOverlayVisible(visible: boolean) {
|
||||
overlay.classList.toggle('hidden', !visible);
|
||||
canvas.style.pointerEvents = visible ? 'none' : 'auto';
|
||||
document.body.classList.toggle('gameplay-active', !visible);
|
||||
updateMobileMenuButtonVisibility();
|
||||
updateFullscreenButtonVisibility();
|
||||
syncTouchPreviewVisibility();
|
||||
}
|
||||
|
||||
@@ -934,10 +954,18 @@ function maybeUpdateControlModeSettings(now: number) {
|
||||
function syncTouchPreviewVisibility() {
|
||||
const overlayVisible = !overlay.classList.contains('hidden');
|
||||
const mode = controlModeSelect?.value;
|
||||
const shouldPreview = overlayVisible && mode === 'touch';
|
||||
const shouldPreview = overlayVisible && mode === 'touch' && !isOverlayPanelNearBottom();
|
||||
game.input?.setTouchPreview?.(shouldPreview);
|
||||
}
|
||||
|
||||
function isOverlayPanelNearBottom() {
|
||||
if (!overlayPanel) {
|
||||
return false;
|
||||
}
|
||||
const buffer = 24;
|
||||
return overlayPanel.scrollTop + overlayPanel.clientHeight >= overlayPanel.scrollHeight - buffer;
|
||||
}
|
||||
|
||||
function renderFrame(now: number) {
|
||||
requestAnimationFrame(renderFrame);
|
||||
|
||||
@@ -1118,6 +1146,7 @@ bindRangeControl(
|
||||
updateControlModeSettingsVisibility();
|
||||
updateFalloffCurve(game.input?.inputFalloff ?? 1);
|
||||
syncTouchPreviewVisibility();
|
||||
updateFullscreenButtonVisibility();
|
||||
|
||||
smb2ModeSelect?.addEventListener('change', () => {
|
||||
updateSmb2ModeFields();
|
||||
@@ -1140,6 +1169,40 @@ controlModeSelect?.addEventListener('change', () => {
|
||||
syncTouchPreviewVisibility();
|
||||
});
|
||||
|
||||
fullscreenButton?.addEventListener('click', async () => {
|
||||
const root = document.documentElement as HTMLElement & {
|
||||
webkitRequestFullscreen?: () => Promise<void> | void;
|
||||
};
|
||||
try {
|
||||
if (document.fullscreenElement || (document as typeof document & { webkitFullscreenElement?: Element | null }).webkitFullscreenElement) {
|
||||
if (document.exitFullscreen) {
|
||||
await document.exitFullscreen();
|
||||
} else if ((document as typeof document & { webkitExitFullscreen?: () => Promise<void> | void }).webkitExitFullscreen) {
|
||||
await (document as typeof document & { webkitExitFullscreen?: () => Promise<void> | void }).webkitExitFullscreen?.();
|
||||
}
|
||||
} else if (root.requestFullscreen) {
|
||||
await root.requestFullscreen();
|
||||
} else if (root.webkitRequestFullscreen) {
|
||||
await root.webkitRequestFullscreen();
|
||||
}
|
||||
} catch {
|
||||
// Ignore fullscreen errors.
|
||||
}
|
||||
updateFullscreenButtonVisibility();
|
||||
});
|
||||
|
||||
document.addEventListener('fullscreenchange', () => {
|
||||
updateFullscreenButtonVisibility();
|
||||
});
|
||||
|
||||
document.addEventListener('webkitfullscreenchange', () => {
|
||||
updateFullscreenButtonVisibility();
|
||||
});
|
||||
|
||||
overlayPanel?.addEventListener('scroll', () => {
|
||||
syncTouchPreviewVisibility();
|
||||
});
|
||||
|
||||
gamepadCalibrationButton?.addEventListener('click', () => {
|
||||
startGamepadCalibration();
|
||||
});
|
||||
|
||||
11
style.css
11
style.css
@@ -550,7 +550,7 @@ button:disabled {
|
||||
top: 12px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: min(420px, 92vw);
|
||||
width: min(220px, 48vw);
|
||||
height: 40px;
|
||||
padding: 6px 16px;
|
||||
border-radius: 999px;
|
||||
@@ -565,3 +565,12 @@ button:disabled {
|
||||
z-index: 9;
|
||||
backdrop-filter: blur(6px);
|
||||
}
|
||||
|
||||
.fullscreen-button {
|
||||
position: fixed;
|
||||
top: 8px;
|
||||
right: 8px;
|
||||
z-index: 12;
|
||||
padding: 6px 10px;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user