О чем этот пример
В Phaser управление сценами часто требует передачи контекста между ними. Особенно это касается аудио, которое может быть загружено в одной сцене, а воспроизведено в другой. Этот пример показывает, как организовать загрузку и воспроизведение звукового файла из дочерней сцены после остановки музыки из родительской. Это полезно для создания последовательных игровых меню, переходов между уровнями или кат-сцен, где каждый этап имеет свой саундтрек.
Версия Phaser: код и демо в этой статье рассчитаны на Phaser 3.90.0.
Живой запуск
Ниже встроен рабочий билд примера. Оригинальный источник: GitHub.
Исходный код
class SceneA extends Phaser.Scene
{
jungle;
constructor ()
{
super({ key: 'sceneA' });
}
preload ()
{
this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.audio('jungle', [
'assets/audio/jungle.ogg',
'assets/audio/jungle.mp3'
]);
this.load.image('wizball', 'assets/sprites/wizball.png');
this.load.bitmapFont('atari-classic', 'assets/fonts/bitmap/atari-classic.png', 'assets/fonts/bitmap/atari-classic.xml');
}
create ()
{
console.log('SceneA');
const text = this.add.bitmapText(400, 100, 'atari-classic', '', 30)
.setOrigin(0.5);
this.add.image(400, 300, 'wizball');
this.jungle = this.sound.add('jungle');
this.jungle.play({
loop: true
});
if (this.sound.locked)
{
text.setText('Tap to unlock\nand play music');
this.sound.once('unlocked', function (soundManager)
{
this.setupSceneInput(text);
}, this);
}
else
{
this.setupSceneInput(text);
}
}
setupSceneInput (text)
{
text.setText(' Tap to load and play\nmusic from child scene');
this.input.once('pointerup', function ()
{
this.jungle.stop();
this.scene.start('sceneB');
}, this);
}
}
class SceneB extends Phaser.Scene
{
constructor ()
{
super({ key: 'sceneB' });
}
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'
]);
}
create ()
{
console.log('SceneB');
this.scene.start('sceneC');
}
}
class SceneC extends Phaser.Scene
{
constructor ()
{
super({ key: 'sceneC' });
}
create ()
{
console.log('SceneC');
this.add.image(400, 300, 'wizball').setScale(4);
const music = this.sound.add('theme');
music.play({
loop: true
});
if (this.sound.locked)
{
const text = this.add.bitmapText(400, 100, 'atari-classic',
'Tap to unlock and play\nmusic from child scene', 30)
.setOrigin(0.5);
this.sound.once('unlocked', soundManager =>
{
text.visible = false;
}, this);
}
}
}
const config = {
type: Phaser.AUTO,
parent: 'phaser-example',
width: 800,
height: 600,
pixelArt: true,
scene: [ SceneA, SceneB, SceneC ]
};
const game = new Phaser.Game(config);
Загрузка аудио в родительской сцене
В SceneA мы загружаем начальный аудиофайл и спрайт. Обратите внимание на использование метода this.load.audio с массивом путей для поддержки разных форматов (OGG и MP3). Это обеспечивает кроссбраузерность.
preload ()
{
this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.audio('jungle', [
'assets/audio/jungle.ogg',
'assets/audio/jungle.mp3'
]);
this.load.image('wizball', 'assets/sprites/wizball.png');
}
В методе create создается объект звука this.jungle с помощью this.sound.add. Он воспроизводится с параметром loop: true. Важный момент — проверка свойства this.sound.locked. Оно указывает, требуется ли пользовательское взаимодействие (например, клик) для разблокировки аудио в браузере. Если звук заблокирован, мы показываем подсказку и ждем события unlocked.
Остановка звука и запуск дочерней сцены
После разблокировки (или если блокировки не было) вызывается метод setupSceneInput. Он меняет текст подсказки и назначает обработчик на клик. По клику происходит ключевое действие: остановка текущей музыки и запуск следующей сцены.
setupSceneInput (text)
{
text.setText(' Tap to load and play\nmusic from child scene');
this.input.once('pointerup', function ()
{
this.jungle.stop();
this.scene.start('sceneB');
}, this);
}
Метод this.jungle.stop() гарантирует, что музыка из SceneA не будет накладываться на звук из следующей сцены. this.scene.start('sceneB') полностью останавливает SceneA и запускает SceneB. Это важно, так как SceneB выступает промежуточным звеном для загрузки нового аудиоресурса.
Промежуточная сцена для загрузки
SceneB — это техническая сцена, основная задача которой — загрузить новый аудиофайл. Она не содержит логики отображения. В preload загружается аудио с ключом 'theme', а в create сразу запускается SceneC.
create ()
{
console.log('SceneB');
this.scene.start('sceneC');
}
Такой подход разделяет ответственность: SceneA управляет первым звуком и интерфейсом, SceneB загружает данные, а SceneC их использует. Это чистая архитектура, упрощающая отладку.
Воспроизведение звука в целевой сцене
SceneC — конечная точка. Она использует спрайт, загруженный еще в SceneA (ресурсы в Phaser глобальны для всего экземпляра Game). Здесь создается и воспроизводится звук 'theme', который был загружен в SceneB.
create ()
{
const music = this.sound.add('theme');
music.play({
loop: true
});
if (this.sound.locked)
{
// ... показ текста и подписка на 'unlocked'
}
}
Обратите внимание, что здесь снова проверяется this.sound.locked. Хотя аудио уже было разблокировано в SceneA, эта проверка является хорошей практикой на случай, если пользователь перезагрузит страницу или перейдет прямо в SceneC в другом сценарии.
Конфигурация игры и глобальный доступ к аудио
Все три сцены передаются в массив scene конфигурации игры. Phaser инициализирует их и создает глобальный менеджер звуков this.sound в каждой сцене.
const config = {
type: Phaser.AUTO,
scene: [ SceneA, SceneB, SceneC ]
};
Ключевой вывод: аудио, загруженное в одной сцене с помощью this.load.audio, становится доступно во всех других сценах через this.sound.add('key'). Это позволяет эффективно разделять загрузку ресурсов и их использование.
Что попробовать дальше
Пример демонстрирует паттерн последовательной загрузки и воспроизведения аудио через цепочку сцен. Это основа для создания сложных аудиопоследовательностей. Для экспериментов попробуйте
- использовать
this.scene.switchвместоthis.scene.start, чтобы сохранять состояние предыдущей сцены - создать глобальный менеджер аудио в отдельном плагине или сцене
- добавить fade-in/fade-out эффекты при переходе между треками с помощью
this.tweens.addи свойстваvolumeобъекта Sound
