О чем этот пример
Если ваша игра на Phaser внезапно замолкает на iPhone или iPad, вы столкнулись с известной проблемой политики автовоспроизведения аудио в Safari. iOS блокирует любое звуковое сопровождение, которое не было инициировано прямым действием пользователя (например, тапом по экрану). В этой статье мы разберем конкретный пример кода, демонстрирующий проблему, и предложим надежные стратегии для ее обхода, чтобы ваша музыка и звуковые эффекты гарантированно работали на всех устройствах.
Версия Phaser: код и демо в этой статье рассчитаны на Phaser 3.90.0.
Живой запуск
Ниже встроен рабочий билд примера. Оригинальный источник: GitHub.
Исходный код
class Example extends Phaser.Scene
{
preload ()
{
this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.audio('theme', [
'assets/audio/oedipus_wizball_highscore.ogg',
'assets/audio/oedipus_wizball_highscore.mp3'
]);
this.load.image('wizball', 'assets/sprites/wizball.png');
}
create ()
{
this.add.image(400, 300, 'wizball').setScale(4);
const music = this.sound.add('theme');
console.log(music);
console.log('play?', music.play());
this.sound.pauseOnBlur = true;
}
}
const config = {
type: Phaser.AUTO,
parent: 'phaser-example',
width: 800,
height: 600,
pixelArt: true,
scene: Example
};
const game = new Phaser.Game(config);
В чем суть проблемы?
Исходный код загружает аудиофайл и пытается воспроизвести его сразу в методе create(), который выполняется автоматически после загрузки ресурсов. На десктопных браузерах и Android это обычно работает. Однако iOS (Safari) требует, чтобы первый аудиоконтекст был создан строго в результате прямого жеста пользователя (touch event).
Ключевые строки, вызывающие проблему:
const music = this.sound.add('theme');
console.log('play?', music.play());
Вызов music.play() в данном контексте на iOS, скорее всего, вернет false, а звук воспроизводиться не будет. Флаг this.sound.pauseOnBlur = true здесь не влияет на саму проблему автовоспроизведения.
Важно понимать, что проблема не в Phaser как таковом, а в ограничениях браузера, с которыми фреймворк вынужден считаться.
Решение 1: Воспроизведение по первому касанию
Самый надежный и рекомендуемый способ — отложить создание и воспроизведение звука до первого взаимодействия пользователя с игрой. Для этого нужно добавить одноразовый обработчик события касания или клика.
Модифицируем метод create() из примера:
create ()
{
this.add.image(400, 300, 'wizball').setScale(4);
this.sound.pauseOnBlur = true;
// Создаем объект звука, но НЕ воспроизводим его сразу
this.music = this.sound.add('theme');
// Добавляем одноразовый обработчик на весь холст игры
this.input.once('pointerdown', () => {
console.log('play?', this.music.play());
});
}
Теперь аудиоконтекст будет создан в момент первого касания пользователя по экрану, что полностью соответствует требованиям iOS. Обратите внимание на использование this.input.once, чтобы обработчик сработал только один раз. Объект звука this.music создается заранее, чтобы минимизировать задержку при первом воспроизведении.
Решение 2: Использование внутриигровой кнопки
Более удобный для пользователя подход — создать визуальную кнопку (например, «Включить звук»), которая станет частью геймдизайна. Это явное действие пользователя, которое гарантированно разблокирует аудио.
Пример добавления кнопки:
create ()
{
this.add.image(400, 300, 'wizball').setScale(4);
this.sound.pauseOnBlur = true;
this.music = this.sound.add('theme');
// Создаем текстовую кнопку
const startButton = this.add.text(400, 500, 'Включить музыку', {
fontSize: '32px',
fill: '#fff',
backgroundColor: '#333',
padding: { x: 20, y: 10 }
})
.setOrigin(0.5)
.setInteractive({ useHandCursor: true });
// Назначаем обработчик на кнопку
startButton.on('pointerdown', () => {
console.log('play?', this.music.play());
startButton.setVisible(false); // Скрываем кнопку после нажатия
});
}
Это решение не только технически корректно, но и дает игроку контроль, что считается хорошей практикой в веб-разработке. Вы можете стилизовать кнопку как угодно, используя возможности this.add.text или загрузив спрайт.
Важные нюансы и API Phaser Sound
Работая со звуком в Phaser, помните о следующих моментах:
* `this.sound.add(key)` создает объект `Sound` (или `WebAudioSound`, `HTML5AudioSound`), но не запускает воспроизведение. Это безопасно делать до пользовательского действия.
* Метод `.play()` возвращает `true`, если воспроизведение началось успешно, и `false` в случае неудачи (как на iOS без жеста).
* Флаг `this.sound.pauseOnBlur = true` очень полезен для мобильных игр. Он автоматически ставит все звуки на паузу, когда пользователь сворачивает браузер или переключается на другую вкладку, и возобновляет их при возвращении.
* Для фоновой музыки, которая должна зацикливаться, используйте конфиг в методе `play()`:
this.music.play({ loop: true, volume: 0.5 });
* Все звуки, созданные через this.sound.add, автоматически управляются глобальным менеджером this.sound. Вы можете остановить все звуки разом: this.sound.stopAll().
Что попробовать дальше
Проблема воспроизведения аудио на iOS — это не баг, а особенность, с которой нужно правильно работать. Решение заключается в том, чтобы первое воспроизведение любого звука было инициировано жестом пользователя. Самый простой и надежный способ — использовать this.input.once('pointerdown', handler).
Для экспериментов попробуйте:
1. Создать отдельную сцену-заставку с единственной кнопкой «Играть», которая запускает основную сцену и фоновую музыку.
2. Реализовать систему, где первый звук — это короткий беззвучный (volume: 0) семпл, воспроизводимый по касанию, чтобы «разблокировать» аудиосистему для последующих звуков.
3. Проверить работу на разных устройствах, используя инструменты разработчика Safari в режиме эмуляции iOS.
