Files
cacto f2a5657740 Звук (Web Audio API) + несколько раскладок coop-карты
Клиент: audio.js синтезирует эффекты без внешних файлов (выстрел,
взрыв, разрушение кирпича, победа/поражение) — вписывается в
"чистый HTML/CSS/JS" стек. AudioContext разблокируется по клику
"готов" (браузеры блокируют автовоспроизведение без жеста).

Сервер: 3 варианта раскладки coop-карты (случайный выбор при старте
матча) вместо одной фиксированной.

Fix: сервер разрушал кирпичи в своей копии карты, но никогда не сообщал
об этом клиенту — стена оставалась видимой навсегда, хотя сквозь неё уже
можно было стрелять. Теперь game.state включает destroyed_tiles,
клиент убирает соответствующий спрайт стены.
2026-08-06 12:42:52 +05:00

77 lines
2.4 KiB
JavaScript

// Простой синтез звука через 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)
);
}