sound at game mode
This commit is contained in:
parent
d42e47b15b
commit
0734364269
2 changed files with 135 additions and 1 deletions
|
|
@ -1,5 +1,6 @@
|
|||
import { useState, useEffect, useRef, useCallback } from 'react'
|
||||
import styles from '../styles/game.module.css'
|
||||
import { playKeyClick, playWordComplete, playCombo, playMiss, playGameOver } from '../sounds'
|
||||
|
||||
const WORD_POOL = [
|
||||
'the', 'and', 'for', 'are', 'but', 'not', 'you', 'all', 'can', 'her',
|
||||
|
|
@ -152,6 +153,7 @@ export function GameMode({ highScore, onGameOver }: Props) {
|
|||
}
|
||||
}
|
||||
if (lostLife) {
|
||||
playMiss()
|
||||
setLives(l => {
|
||||
const nl = l - 1
|
||||
if (nl <= 0) {
|
||||
|
|
@ -176,6 +178,7 @@ export function GameMode({ highScore, onGameOver }: Props) {
|
|||
// Fire onGameOver
|
||||
useEffect(() => {
|
||||
if (gameOver && started) {
|
||||
playGameOver()
|
||||
onGameOver(score)
|
||||
}
|
||||
}, [gameOver, started, score, onGameOver])
|
||||
|
|
@ -204,6 +207,7 @@ export function GameMode({ highScore, onGameOver }: Props) {
|
|||
|
||||
if (e.key.length !== 1) return
|
||||
e.preventDefault()
|
||||
playKeyClick()
|
||||
|
||||
const newInput = inputRef.current + e.key
|
||||
|
||||
|
|
@ -214,6 +218,7 @@ export function GameMode({ highScore, onGameOver }: Props) {
|
|||
const points = completed.text.length * 10
|
||||
setScore(s => s + points)
|
||||
updateInput('')
|
||||
playWordComplete()
|
||||
|
||||
// Spawn explosion at word position
|
||||
const now = Date.now()
|
||||
|
|
@ -231,7 +236,10 @@ export function GameMode({ highScore, onGameOver }: Props) {
|
|||
// Combo tracking
|
||||
setCombo(c => {
|
||||
const next = c + 1
|
||||
if (next >= 3) setShowCombo(now)
|
||||
if (next >= 3) {
|
||||
setShowCombo(now)
|
||||
playCombo()
|
||||
}
|
||||
return next
|
||||
})
|
||||
clearTimeout(comboTimerRef.current)
|
||||
|
|
|
|||
126
src/sounds.ts
Normal file
126
src/sounds.ts
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
// Synthesized game sounds using Web Audio API — no audio files needed
|
||||
|
||||
let ctx: AudioContext | null = null
|
||||
|
||||
function getCtx(): AudioContext {
|
||||
if (!ctx) ctx = new AudioContext()
|
||||
if (ctx.state === 'suspended') ctx.resume()
|
||||
return ctx
|
||||
}
|
||||
|
||||
export function playKeyClick() {
|
||||
const c = getCtx()
|
||||
const osc = c.createOscillator()
|
||||
const gain = c.createGain()
|
||||
osc.type = 'square'
|
||||
osc.frequency.setValueAtTime(800, c.currentTime)
|
||||
osc.frequency.exponentialRampToValueAtTime(600, c.currentTime + 0.04)
|
||||
gain.gain.setValueAtTime(0.06, c.currentTime)
|
||||
gain.gain.exponentialRampToValueAtTime(0.001, c.currentTime + 0.04)
|
||||
osc.connect(gain).connect(c.destination)
|
||||
osc.start()
|
||||
osc.stop(c.currentTime + 0.04)
|
||||
}
|
||||
|
||||
export function playWordComplete() {
|
||||
const c = getCtx()
|
||||
const t = c.currentTime
|
||||
|
||||
// Bright rising arpeggio
|
||||
const notes = [523, 659, 784, 1047] // C5 E5 G5 C6
|
||||
notes.forEach((freq, i) => {
|
||||
const osc = c.createOscillator()
|
||||
const gain = c.createGain()
|
||||
osc.type = 'sine'
|
||||
osc.frequency.setValueAtTime(freq, t + i * 0.06)
|
||||
gain.gain.setValueAtTime(0, t)
|
||||
gain.gain.linearRampToValueAtTime(0.15, t + i * 0.06)
|
||||
gain.gain.exponentialRampToValueAtTime(0.001, t + i * 0.06 + 0.2)
|
||||
osc.connect(gain).connect(c.destination)
|
||||
osc.start(t + i * 0.06)
|
||||
osc.stop(t + i * 0.06 + 0.2)
|
||||
})
|
||||
}
|
||||
|
||||
export function playCombo() {
|
||||
const c = getCtx()
|
||||
const t = c.currentTime
|
||||
|
||||
// Power-up sweep
|
||||
const osc = c.createOscillator()
|
||||
const gain = c.createGain()
|
||||
osc.type = 'sawtooth'
|
||||
osc.frequency.setValueAtTime(300, t)
|
||||
osc.frequency.exponentialRampToValueAtTime(1200, t + 0.15)
|
||||
osc.frequency.exponentialRampToValueAtTime(800, t + 0.3)
|
||||
gain.gain.setValueAtTime(0.1, t)
|
||||
gain.gain.linearRampToValueAtTime(0.15, t + 0.1)
|
||||
gain.gain.exponentialRampToValueAtTime(0.001, t + 0.35)
|
||||
osc.connect(gain).connect(c.destination)
|
||||
osc.start(t)
|
||||
osc.stop(t + 0.35)
|
||||
|
||||
// Sparkle overtone
|
||||
const osc2 = c.createOscillator()
|
||||
const gain2 = c.createGain()
|
||||
osc2.type = 'sine'
|
||||
osc2.frequency.setValueAtTime(1600, t + 0.05)
|
||||
osc2.frequency.exponentialRampToValueAtTime(2400, t + 0.2)
|
||||
gain2.gain.setValueAtTime(0.08, t + 0.05)
|
||||
gain2.gain.exponentialRampToValueAtTime(0.001, t + 0.35)
|
||||
osc2.connect(gain2).connect(c.destination)
|
||||
osc2.start(t + 0.05)
|
||||
osc2.stop(t + 0.35)
|
||||
}
|
||||
|
||||
export function playMiss() {
|
||||
const c = getCtx()
|
||||
const t = c.currentTime
|
||||
|
||||
// Low thud
|
||||
const osc = c.createOscillator()
|
||||
const gain = c.createGain()
|
||||
osc.type = 'sine'
|
||||
osc.frequency.setValueAtTime(150, t)
|
||||
osc.frequency.exponentialRampToValueAtTime(60, t + 0.2)
|
||||
gain.gain.setValueAtTime(0.2, t)
|
||||
gain.gain.exponentialRampToValueAtTime(0.001, t + 0.25)
|
||||
osc.connect(gain).connect(c.destination)
|
||||
osc.start(t)
|
||||
osc.stop(t + 0.25)
|
||||
|
||||
// Noise burst
|
||||
const bufferSize = c.sampleRate * 0.1
|
||||
const buffer = c.createBuffer(1, bufferSize, c.sampleRate)
|
||||
const data = buffer.getChannelData(0)
|
||||
for (let i = 0; i < bufferSize; i++) {
|
||||
data[i] = (Math.random() * 2 - 1) * (1 - i / bufferSize)
|
||||
}
|
||||
const noise = c.createBufferSource()
|
||||
const noiseGain = c.createGain()
|
||||
noise.buffer = buffer
|
||||
noiseGain.gain.setValueAtTime(0.08, t)
|
||||
noiseGain.gain.exponentialRampToValueAtTime(0.001, t + 0.1)
|
||||
noise.connect(noiseGain).connect(c.destination)
|
||||
noise.start(t)
|
||||
}
|
||||
|
||||
export function playGameOver() {
|
||||
const c = getCtx()
|
||||
const t = c.currentTime
|
||||
|
||||
// Descending minor arpeggio
|
||||
const notes = [440, 349, 294, 220] // A4 F4 D4 A3
|
||||
notes.forEach((freq, i) => {
|
||||
const osc = c.createOscillator()
|
||||
const gain = c.createGain()
|
||||
osc.type = 'triangle'
|
||||
osc.frequency.setValueAtTime(freq, t + i * 0.15)
|
||||
gain.gain.setValueAtTime(0, t)
|
||||
gain.gain.linearRampToValueAtTime(0.15, t + i * 0.15)
|
||||
gain.gain.exponentialRampToValueAtTime(0.001, t + i * 0.15 + 0.4)
|
||||
osc.connect(gain).connect(c.destination)
|
||||
osc.start(t + i * 0.15)
|
||||
osc.stop(t + i * 0.15 + 0.4)
|
||||
})
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue