Add sound effects, background music and a sound menu
Generate retro sound effects and music in the browser with the Web Audio API, so no audio files are needed. Any sound can still be replaced by a file. Audio (src/audio/): - 16 effects (move, rotate, hold, hard drop, lock, line clears, garbage, items, countdown, knockout, win/lose), triggered by comparing match snapshots, so the server is unchanged - Background music as a shuffled playlist of any number of tracks, either synth songs or audio files; 3 original songs included, tempo rises with the level - SoundPlayer interface, so the audio engine can be replaced - Audio files and music tracks are registered in theme/assets.ts (soundFiles, musicTracks) - All volume levels for developers are in config/sound.ts (master, sfx, music, per effect, per track, default slider positions) UI: - Speaker button fixed in the top right corner of every screen; opens an inline strip with a mute button and volume slider for SFX and music that doesn't cover the boards or pause the game - Leave button below it in the lobby and the game; in a running match it asks for a second click because leaving gives up the match - Lobby Music panel to switch single tracks on or off - Keys: M all sound on/off, B music on/off, N next track - Settings are saved in localStorage Docs: audio section in frontend.md, sound and music guide in extending.md, new keys in game-rules.md and the README.
This commit is contained in:
@@ -0,0 +1,47 @@
|
||||
import { useEffect, useState, type MouseEvent } from 'react';
|
||||
import { SoundMenu } from './SoundMenu';
|
||||
import { Button } from './ui/Button';
|
||||
import styles from './CornerControls.module.css';
|
||||
|
||||
const CONFIRM_MS = 3000;
|
||||
|
||||
interface Props {
|
||||
/** Shows a Leave button below the sound button. */
|
||||
onLeave?: () => void;
|
||||
/** Ask for a second click before leaving, e.g. because leaving forfeits a running match. */
|
||||
leaveConfirm?: boolean;
|
||||
}
|
||||
|
||||
/** Sound menu and optional Leave button, fixed in the upper right corner on every screen. */
|
||||
export function CornerControls({ onLeave, leaveConfirm = false }: Props) {
|
||||
const [confirming, setConfirming] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (!confirming) return;
|
||||
const timer = window.setTimeout(() => setConfirming(false), CONFIRM_MS);
|
||||
return () => window.clearTimeout(timer);
|
||||
}, [confirming]);
|
||||
|
||||
const onLeaveClick = (e: MouseEvent<HTMLButtonElement>) => {
|
||||
// Keep keyboard focus off the button so Space (hard drop) doesn't press it.
|
||||
e.currentTarget.blur();
|
||||
if (leaveConfirm && !confirming) setConfirming(true);
|
||||
else onLeave?.();
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={styles.corner}>
|
||||
<SoundMenu />
|
||||
{onLeave && (
|
||||
<Button
|
||||
size="small"
|
||||
variant={confirming ? 'danger' : 'secondary'}
|
||||
onClick={onLeaveClick}
|
||||
title={leaveConfirm ? 'Leaving gives up the running match' : 'Leave the lobby'}
|
||||
>
|
||||
{confirming ? 'Sure?' : 'Leave'}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user