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 { useState, useEffect, useRef, useCallback } from 'react'
|
||||||
import styles from '../styles/game.module.css'
|
import styles from '../styles/game.module.css'
|
||||||
|
import { playKeyClick, playWordComplete, playCombo, playMiss, playGameOver } from '../sounds'
|
||||||
|
|
||||||
const WORD_POOL = [
|
const WORD_POOL = [
|
||||||
'the', 'and', 'for', 'are', 'but', 'not', 'you', 'all', 'can', 'her',
|
'the', 'and', 'for', 'are', 'but', 'not', 'you', 'all', 'can', 'her',
|
||||||
|
|
@ -152,6 +153,7 @@ export function GameMode({ highScore, onGameOver }: Props) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (lostLife) {
|
if (lostLife) {
|
||||||
|
playMiss()
|
||||||
setLives(l => {
|
setLives(l => {
|
||||||
const nl = l - 1
|
const nl = l - 1
|
||||||
if (nl <= 0) {
|
if (nl <= 0) {
|
||||||
|
|
@ -176,6 +178,7 @@ export function GameMode({ highScore, onGameOver }: Props) {
|
||||||
// Fire onGameOver
|
// Fire onGameOver
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (gameOver && started) {
|
if (gameOver && started) {
|
||||||
|
playGameOver()
|
||||||
onGameOver(score)
|
onGameOver(score)
|
||||||
}
|
}
|
||||||
}, [gameOver, started, score, onGameOver])
|
}, [gameOver, started, score, onGameOver])
|
||||||
|
|
@ -204,6 +207,7 @@ export function GameMode({ highScore, onGameOver }: Props) {
|
||||||
|
|
||||||
if (e.key.length !== 1) return
|
if (e.key.length !== 1) return
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
|
playKeyClick()
|
||||||
|
|
||||||
const newInput = inputRef.current + e.key
|
const newInput = inputRef.current + e.key
|
||||||
|
|
||||||
|
|
@ -214,6 +218,7 @@ export function GameMode({ highScore, onGameOver }: Props) {
|
||||||
const points = completed.text.length * 10
|
const points = completed.text.length * 10
|
||||||
setScore(s => s + points)
|
setScore(s => s + points)
|
||||||
updateInput('')
|
updateInput('')
|
||||||
|
playWordComplete()
|
||||||
|
|
||||||
// Spawn explosion at word position
|
// Spawn explosion at word position
|
||||||
const now = Date.now()
|
const now = Date.now()
|
||||||
|
|
@ -231,7 +236,10 @@ export function GameMode({ highScore, onGameOver }: Props) {
|
||||||
// Combo tracking
|
// Combo tracking
|
||||||
setCombo(c => {
|
setCombo(c => {
|
||||||
const next = c + 1
|
const next = c + 1
|
||||||
if (next >= 3) setShowCombo(now)
|
if (next >= 3) {
|
||||||
|
setShowCombo(now)
|
||||||
|
playCombo()
|
||||||
|
}
|
||||||
return next
|
return next
|
||||||
})
|
})
|
||||||
clearTimeout(comboTimerRef.current)
|
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