О чем этот пример
При разработке браузерных игр на Phaser вы можете столкнуться с ситуацией, когда аудио перестаёт воспроизводиться после перезагрузки сцены или перезапуска игры. Это происходит из-за политики браузеров, которые блокируют создание новых аудиоконтекстов без явного взаимодействия пользователя. В этой статье мы разберём практический пример, показывающий, как правильно переиспользовать один AudioContext на протяжении всего жизненного цикла приложения, что гарантирует стабильную работу звука.
Версия Phaser: код и демо в этой статье рассчитаны на Phaser 3.90.0.
Живой запуск
Ниже встроен рабочий билд примера. Оригинальный источник: GitHub.
Исходный код
class Example extends Phaser.Scene
{
audioContext;
preload ()
{
this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.spritesheet('explosion', 'assets/atlas/trimsheet/explosion.png', { frameWidth: 64, frameHeight: 64 });
this.load.spritesheet('bomb', 'assets/sprites/xenon2_bomb.png', { frameWidth: 8, frameHeight: 16 });
this.load.audio('explosion', [ 'assets/audio/SoundEffects/explosion.mp3' ]);
}
create ()
{
this.anims.create({
key: 'rotate',
frames: this.anims.generateFrameNumbers('bomb', { start: 0, end: 3, first: 3 }),
frameRate: 20,
repeat: -1
});
this.anims.create({
key: 'explode',
frames: this.anims.generateFrameNumbers('explosion', { start: 0, end: 23, first: 23 }),
frameRate: 20
});
const bomb = this.add.sprite(400, 300, 'bomb');
bomb.setScale(6, -6);
bomb.anims.play('rotate');
this.input.once('pointerdown', function ()
{
bomb.visible = false;
const boom = this.add.sprite(400, 300, 'explosion');
boom.setScale(6);
boom.anims.play('explode');
const explosion = this.sound.add('explosion', {
volume: 0.5
});
explosion.on('complete', function (sound)
{
setTimeout(() =>
{
this.sys.game.destroy(true);
document.addEventListener('mousedown', function newGame ()
{
game = new Phaser.Game(config);
document.removeEventListener('mousedown', newGame);
});
});
}, this);
explosion.play();
}, this);
}
}
try
{
this.audioContext = new (window.AudioContext || window.webkitAudioContext)();
}
catch (e)
{
console.error(e);
}
const config = {
type: Phaser.AUTO,
width: 800,
height: 600,
parent: 'phaser-example',
scene: Example,
pixelArt: true,
audio: {
context: this.audioContext
}
};
let game = new Phaser.Game(config);
Проблема: Браузер блокирует аудио
Современные браузеры (Chrome, Firefox и др.) требуют, чтобы воспроизведение аудио было инициировано действием пользователя (клик, нажатие клавиши). Это защита от навязчивого авто-воспроизведения. Однако, если вы создаёте новый AudioContext после уничтожения игры (например, при перезапуске), браузер может заблокировать его создание, так как это действие уже не связано с прямым жестом пользователя.
Код вне класса Example демонстрирует попытку создать контекст глобально:
Решение: Создание и передача контекста в конфиг
Решение заключается в создании аудиоконтекста один раз, в глобальной области видимости, до инициализации игры Phaser. Затем этот контекст передаётся в конфигурацию игры. Phaser будет использовать предоставленный контекст, а не пытаться создать новый при каждом перезапуске.
try
{
this.audioContext = new (window.AudioContext || window.webkitAudioContext)();
}
catch (e)
{
console.error(e);
}
const config = {
// ... другие настройки ...
audio: {
context: this.audioContext // Передаём существующий контекст
}
};
Ключевой момент — свойство context внутри объекта audio конфигурации. Phaser принимает его и использует для всей внутренней работы со звуком.
Механика перезапуска игры
В примере игра завершается взрывом бомбы. После завершения звука взрыва игра уничтожается, но глобальный audioContext остаётся жив. Новый клик пользователя создаёт экземпляр игры заново, используя тот же самый аудиоконтекст.
Код в обработчике события complete звука взрыва:
explosion.on('complete', function (sound)
{
setTimeout(() =>
{
// Уничтожаем текущий экземпляр игры
this.sys.game.destroy(true);
// Ждём нового клика пользователя для перезапуска
document.addEventListener('mousedown', function newGame ()
{
// Создаём новую игру, конфиг уже содержит наш audioContext
game = new Phaser.Game(config);
document.removeEventListener('mousedown', newGame);
});
});
}, this);
Обратите внимание: this.sys.game.destroy(true) уничтожает игровой инстанс, но не трогает объект audioContext, созданный ранее. Новый Phaser.Game получает его из конфига.
Загрузка и воспроизведение звука
Звуковой файл загружается стандартным для Phaser способом в preload(). В create() звук создаётся как объект Sound с помощью this.sound.add(), и ему задаётся громкость. Важно, что Phaser использует для этого объекта наш переданный в конфиге аудиоконтекст.
// В preload
this.load.audio('explosion', [ 'assets/audio/SoundEffects/explosion.mp3' ]);
// В create, внутри обработчика клика
const explosion = this.sound.add('explosion', {
volume: 0.5 // Устанавливаем громкость
});
explosion.play();
Важные детали реализации
1. **Обработка ошибок:** Создание AudioContext обёрнуто в try...catch, так как некоторые старые браузеры или специфичные настройки могут его не поддерживать.
2. **Проверка поддержки:** Используется конструкция window.AudioContext || window.webkitAudioContext для кросс-браузерной совместимости.
3. **Привязка контекста (this):** В обработчике события pointerdown и в коллбеке on('complete') используется , this) в конце, чтобы сохранить правильный контекст выполнения (экземпляр сцены) внутри функций.
4. **setTimeout при перезапуске:** Небольшая задержка перед уничтожением игры и подпиской на новый клик может помочь избежать потенциальных конфликтов событий.
Что попробовать дальше
Переиспользование одного экземпляра AudioContext — надёжный способ гарантировать работу звука в вашей Phaser-игре, даже при её динамической перезагрузке. Этот подход соответствует требованиям браузеров к взаимодействию с пользователем. Для экспериментов попробуйте: управлять состоянием контекста (приостанавливать suspend() и возобновлять resume() по требованию), создать механизм "пула" звуковых объектов для часто воспроизводимых эффектов или интегрировать этот подход с системой управления звуком в большой игре.
