О чем этот пример
Управление воспроизведением звука — важная часть создания игровой атмосферы. В этом примере Phaser демонстрирует, как работать с аудиоспрайтами, используя маркеры для точного воспроизведения отдельных звуковых эффектов из одного файла. Вы научитесь добавлять маркеры, создавать интерактивные кнопки для их запуска, а также реализуете централизованное управление паузой и возобновлением всего звукового клипа. Этот подход экономит ресурсы и упрощает организацию аудио в проекте.
Версия Phaser: код и демо в этой статье рассчитаны на Phaser 3.90.0.
Живой запуск
Ниже встроен рабочий билд примера. Оригинальный источник: GitHub.
Исходный код
class Example extends Phaser.Scene
{
pauseResumeButtonText;
pauseResumeButton;
fx;
markers = [
{ name: 'charm', start: 0, duration: 2.7, config: {} },
{ name: 'curse', start: 4, duration: 2.9, config: {} },
{ name: 'fireball', start: 8, duration: 5.2, config: {} },
{ name: 'spell', start: 14, duration: 4.7, config: {} },
{ name: 'soundscape', start: 20, duration: 18.8, config: {} }
];
preload ()
{
this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.image('bg', 'assets/pics/cougar-dragonsun.png');
this.load.spritesheet('button', 'assets/ui/flixel-button.png', { frameWidth: 80, frameHeight: 20 });
this.load.bitmapFont('nokia', 'assets/fonts/bitmap/nokia16black.png', 'assets/fonts/bitmap/nokia16black.xml');
this.load.audio('sfx', [
'assets/audio/SoundEffects/magical_horror_audiosprite.ogg',
'assets/audio/SoundEffects/magical_horror_audiosprite.mp3'
]);
}
create ()
{
const bg = this.add.image(400, 300, 'bg');
bg.setScale(800 / bg.width, 600 / bg.height);
this.fx = this.sound.add('sfx');
for (let i = 0; i < this.markers.length; i++)
{
const marker = this.markers[i];
this.fx.addMarker(marker);
this.makeButton.call(this, marker.name, 680, 115 + i * 40);
}
this.makePauseResumeButton.call(this);
this.input.on('gameobjectover', (pointer, button) =>
{
this.setButtonFrame(button, 0);
});
this.input.on('gameobjectout', (pointer, button) =>
{
this.setButtonFrame(button, 1);
});
this.input.on('gameobjectdown', (pointer, button) =>
{
if (button.name === 'pause')
{
if (this.fx.isPaused)
{
this.fx.resume();
}
else if (this.fx.isPlaying)
{
this.fx.pause();
}
else
{
this.setButtonFrame(button, 0);
return;
}
this.setButtonFrame(button, 2);
}
else
{
this.fx.play(button.name);
this.setButtonFrame(button, 2);
}
});
this.input.on('gameobjectup', (pointer, button) =>
{
this.setButtonFrame(button, 0);
});
}
updatePauseResumeButton ()
{
if (this.fx.isPaused)
{
this.pauseResumeButtonText.text = 'resume';
}
else if (this.fx.isPlaying)
{
this.pauseResumeButtonText.text = 'pause';
}
else
{
this.pauseResumeButtonText.text = 'stopped';
}
this.pauseResumeButtonText.x = 640 + (this.pauseResumeButton.width - this.pauseResumeButtonText.width) / 2;
}
makeButton (name, x, y)
{
const button = this.add.image(x, y, 'button', 0).setInteractive();
button.name = name;
button.setScale(2, 1.5);
const text = this.add.bitmapText(x - 40, y - 8, 'nokia', name, 16);
text.x += (button.width - text.width) / 2;
}
makePauseResumeButton ()
{
this.pauseResumeButton = this.add.image(680, 395, 'button', 1).setInteractive();
this.pauseResumeButton.name = 'pause';
this.pauseResumeButton.setScale(2, 1.5);
this.pauseResumeButtonText = this.add.bitmapText(640, 387, 'nokia', '', 16);
}
setButtonFrame (button, frame)
{
button.frame = button.scene.textures.getFrame('button', frame);
}
}
/**
* @author Pavle Goloskokovic <pgoloskokovic@gmail.com> (http://prunegames.com)
*/
const config = {
type: Phaser.AUTO,
parent: 'phaser-example',
width: 800,
height: 600,
scene: Example,
pixelArt: true,
audio: {
noAudio: true
}
};
const game = new Phaser.Game(config);
Что такое аудиомаркеры и зачем они нужны
Аудиоспрайт — это один звуковой файл, содержащий несколько различных эффектов. Чтобы воспроизводить их по отдельности, используются маркеры (markers). Маркер определяет имя, точку начала и длительность фрагмента внутри основного файла.
В примере массив markers описывает пять звуковых эффектов: от 'charm' до 'soundscape'. Каждый маркер загружается в объект звука с помощью метода addMarker().
markers = [
{ name: 'charm', start: 0, duration: 2.7, config: {} },
{ name: 'curse', start: 4, duration: 2.9, config: {} },
// ... другие маркеры
];
// Создание звукового объекта и добавление маркеров
this.fx = this.sound.add('sfx');
for (let i = 0; i < this.markers.length; i++)
{
const marker = this.markers[i];
this.fx.addMarker(marker);
}
После этого можно воспроизводить любой эффект по его имени, например, this.fx.play('fireball'). Это избавляет от необходимости загружать десятки отдельных файлов.
Создание интерактивного интерфейса для управления звуком
Для наглядности в примере создаются кнопки, каждая из которых привязана к своему маркеру. Метод makeButton() создает изображение кнопки из спрайтшита и подписывает его bitmap-шрифтом.
makeButton (name, x, y)
{
const button = this.add.image(x, y, 'button', 0).setInteractive();
button.name = name;
button.setScale(2, 1.5);
const text = this.add.bitmapText(x - 40, y - 8, 'nokia', name, 16);
text.x += (button.width - text.width) / 2;
}
Важно, что свойству button.name присваивается имя маркера. Это позволяет в обработчике событий понять, какую кнопку нажали, и воспроизвести соответствующий звук.
Обработка событий мыши (gameobjectover, gameobjectout, gameobjectdown, gameobjectup) меняет кадр кнопки (0 — обычное состояние, 1 — при наведении, 2 — нажатое), создавая визуальный отклик. Метод setButtonFrame() обновляет отображаемый кадр.
setButtonFrame (button, frame)
{
button.frame = button.scene.textures.getFrame('button', frame);
}
Логика паузы и возобновления воспроизведения
Отдельная кнопка 'pause/resume' управляет глобальным состоянием звукового объекта this.fx. Её текст динамически меняется в зависимости от состояния проигрывателя.
updatePauseResumeButton ()
{
if (this.fx.isPaused)
{
this.pauseResumeButtonText.text = 'resume';
}
else if (this.fx.isPlaying)
{
this.pauseResumeButtonText.text = 'pause';
}
else
{
this.pauseResumeButtonText.text = 'stopped';
}
// ... выравнивание текста
}
В обработчике нажатия gameobjectdown проверяется состояние звука с помощью свойств isPaused и isPlaying. В зависимости от результата вызывается this.fx.pause() или this.fx.resume().
if (button.name === 'pause')
{
if (this.fx.isPaused)
{
this.fx.resume();
}
else if (this.fx.isPlaying)
{
this.fx.pause();
}
// ... обработка случая, когда звук не играет
}
Обратите внимание: методы pause() и resume() работают с воспроизведением всего аудиоспрайта, независимо от того, какой маркер был запущен. Это удобно для глобального управления звуком в игре.
Важная настройка конфигурации: `noAudio`
В конфиге игры присутствует неочевидная, но критически важная настройка для корректной работы примера в среде без реального аудио (например, в некоторых онлайн-песочницах).
const config = {
// ... другие настройки
audio: {
noAudio: true
}
};
Параметр audio.noAudio: true указывает Phaser создать заглушки для аудио API. Это позволяет коду, использующему this.sound, выполняться без ошибок, даже если браузер заблокировал воспроизведение или аудио недоступно. В реальном проекте эту настройку обычно не используют, но она незаменима для демонстраций и тестирования логики.
Что попробовать дальше
Использование аудиоспрайтов с маркерами в Phaser — это мощный и эффективный метод управления звуковыми эффектами. Он сокращает количество HTTP-запросов и упрощает загрузку ресурсов. На основе этого примера можно экспериментировать: создать систему случайного выбора вариаций звука (например, несколько маркеров для шагов), реализовать плавное затухание (fadeOut) при паузе или привязать управление звуком к игровым событиям, а не кнопкам интерфейса. Также стоит изучить config каждого маркера для тонкой настройки громкости или циклического воспроизведения.
