Звук (Web Audio API) + несколько раскладок coop-карты
Клиент: audio.js синтезирует эффекты без внешних файлов (выстрел, взрыв, разрушение кирпича, победа/поражение) — вписывается в "чистый HTML/CSS/JS" стек. AudioContext разблокируется по клику "готов" (браузеры блокируют автовоспроизведение без жеста). Сервер: 3 варианта раскладки coop-карты (случайный выбор при старте матча) вместо одной фиксированной. Fix: сервер разрушал кирпичи в своей копии карты, но никогда не сообщал об этом клиенту — стена оставалась видимой навсегда, хотя сквозь неё уже можно было стрелять. Теперь game.state включает destroyed_tiles, клиент убирает соответствующий спрайт стены.
This commit is contained in:
@@ -0,0 +1,76 @@
|
||||
// Простой синтез звука через Web Audio API — без внешних аудиофайлов.
|
||||
let ctx = null;
|
||||
|
||||
function ensureCtx() {
|
||||
if (!ctx) {
|
||||
const AudioCtx = window.AudioContext || window.webkitAudioContext;
|
||||
ctx = new AudioCtx();
|
||||
}
|
||||
if (ctx.state === 'suspended') {
|
||||
ctx.resume();
|
||||
}
|
||||
return ctx;
|
||||
}
|
||||
|
||||
// Вызывать из обработчика реального пользовательского клика/нажатия —
|
||||
// браузеры блокируют автозапуск звука без явного жеста.
|
||||
export function unlockAudio() {
|
||||
ensureCtx();
|
||||
}
|
||||
|
||||
function beep({ freq, duration = 0.08, type = 'square', gain = 0.1, freqEnd = null }) {
|
||||
const c = ensureCtx();
|
||||
const osc = c.createOscillator();
|
||||
const g = c.createGain();
|
||||
osc.type = type;
|
||||
osc.frequency.setValueAtTime(freq, c.currentTime);
|
||||
if (freqEnd !== null) {
|
||||
osc.frequency.linearRampToValueAtTime(freqEnd, c.currentTime + duration);
|
||||
}
|
||||
g.gain.setValueAtTime(gain, c.currentTime);
|
||||
g.gain.exponentialRampToValueAtTime(0.0001, c.currentTime + duration);
|
||||
osc.connect(g).connect(c.destination);
|
||||
osc.start();
|
||||
osc.stop(c.currentTime + duration);
|
||||
}
|
||||
|
||||
function noiseBurst({ duration = 0.2, gain = 0.18 } = {}) {
|
||||
const c = ensureCtx();
|
||||
const size = Math.max(1, Math.floor(c.sampleRate * duration));
|
||||
const buffer = c.createBuffer(1, size, c.sampleRate);
|
||||
const data = buffer.getChannelData(0);
|
||||
for (let i = 0; i < size; i++) {
|
||||
data[i] = (Math.random() * 2 - 1) * (1 - i / size);
|
||||
}
|
||||
const source = c.createBufferSource();
|
||||
source.buffer = buffer;
|
||||
const g = c.createGain();
|
||||
g.gain.setValueAtTime(gain, c.currentTime);
|
||||
source.connect(g).connect(c.destination);
|
||||
source.start();
|
||||
}
|
||||
|
||||
export function playShot() {
|
||||
beep({ freq: 660, freqEnd: 220, duration: 0.06, type: 'square', gain: 0.08 });
|
||||
}
|
||||
|
||||
export function playWallHit() {
|
||||
noiseBurst({ duration: 0.08, gain: 0.1 });
|
||||
}
|
||||
|
||||
export function playExplosion() {
|
||||
noiseBurst({ duration: 0.25, gain: 0.18 });
|
||||
beep({ freq: 120, freqEnd: 40, duration: 0.25, type: 'sawtooth', gain: 0.1 });
|
||||
}
|
||||
|
||||
export function playWin() {
|
||||
[523, 659, 784, 1047].forEach((freq, i) =>
|
||||
setTimeout(() => beep({ freq, duration: 0.15, type: 'square', gain: 0.1 }), i * 110)
|
||||
);
|
||||
}
|
||||
|
||||
export function playLose() {
|
||||
[392, 330, 262, 196].forEach((freq, i) =>
|
||||
setTimeout(() => beep({ freq, duration: 0.2, type: 'sawtooth', gain: 0.1 }), i * 130)
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user