О чем этот пример
В разработке игр управление звуком — это не просто воспроизведение файлов. Для создания отзывчивого и интерактивного геймплея важно уметь точно контролировать аудиопоток: запускать конкретные эффекты, ставить на паузу и возобновлять воспроизведение. Этот пример демонстрирует, как использовать мощный инструмент Phaser 3 — аудиомаркеры — для организации звуковых эффектов в одном файле и как реализовать логику паузы/возобновления для всего аудиообъекта. Вы научитесь создавать гибкую звуковую систему, которая экономит ресурсы и упрощает управление контентом.
Версия 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);
});
}
update ()
{
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: {
disableWebAudio: true
}
};
const game = new Phaser.Game(config);
Подготовка аудиоресурсов с маркерами
Вместо загрузки множества отдельных файлов для каждого звукового эффекта, Phaser позволяет использовать аудиоспрайты. Это один аудиофайл, внутри которого отмечены временные отрезки (маркеры) для разных звуков.
В методе preload загружается один аудиофайл sfx в двух форматах для кросс-браузерной совместимости. Ключевой элемент — массив markers, где каждый объект описывает один эффект: его имя, время начала и длительность.
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: {} }
];
После загрузки аудио создается объект звука this.fx. В цикле каждый маркер из массива добавляется в этот объект с помощью метода addMarker. Это регистрирует имена эффектов внутри объекта this.fx, позволяя later воспроизводить их по имени.
Создание интерактивного интерфейса
Для наглядного управления звуками создается интерфейс из кнопок. Метод makeButton создает кнопку для каждого звукового маркера.
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;
}
Кнопке присваивается name, соответствующий имени маркера. Это позволяет later в обработчике событий идентифицировать, какой эффект нужно воспроизвести. Отдельно создается кнопка pauseResumeButton для управления паузой и возобновлением всего аудиообъекта.
Обработка кликов и воспроизведение звуков
Логика воспроизведения заключена в обработчиках событий ввода. Событие gameobjectdown срабатывает при нажатии на кнопку.
this.input.on('gameobjectdown', (pointer, button) =>
{
if (button.name === 'pause')
{
// Логика паузы/возобновления
}
else
{
this.fx.play(button.name);
this.setButtonFrame(button, 2);
}
});
Если нажата не кнопка паузы, то вызывается this.fx.play(button.name). Phaser ищет маркер с таким именем внутри объекта this.fx и воспроизводит соответствующий отрезок аудио. Это удобно и эффективно, так как не требует создания отдельных аудиообъектов для каждого эффекта.
Механика паузы и возобновления
Управление состоянием всего аудиообъекта осуществляется через его методы pause() и resume(), а также свойства isPaused и isPlaying.
if (button.name === 'pause')
{
if (this.fx.isPaused)
{
this.fx.resume();
}
else if (this.fx.isPlaying)
{
this.fx.pause();
}
}
Важно различать остановку (stop) и паузу (pause). Пауза сохраняет текущую позицию воспроизведения. При вызове resume() воспроизведение продолжится с этого места. В методе update текст на кнопке динамически меняется в зависимости от состояния this.fx.
if (this.fx.isPaused)
{
this.pauseResumeButtonText.text = 'resume';
}
else if (this.fx.isPlaying)
{
this.pauseResumeButtonText.text = 'pause';
}
Важная деталь конфигурации: HTML5 Audio
Обратите внимание на конфиг игры. В нем явно отключен WebAudio и используется HTML5 Audio API.
const config = {
// ... другие настройки
audio: {
disableWebAudio: true
}
};
Это сделано для совместимости примера, так как маркеры и операции паузы/возобновления для аудиоспрайтов в момент написания статьи стабильно работали через HTML5 Audio. При использовании WebAudio могут потребоваться дополнительные проверки или полифиллы. Всегда тестируйте аудиофункциональность в целевых браузерах.
Что попробовать дальше
Использование аудиомаркеров в Phaser 3 — это профессиональный подход к управлению звуками в игре. Он сокращает количество HTTP-запросов, упрощает загрузку и позволяет тонко контролировать воспроизведение. Для экспериментов попробуйте: создать маркер с циклом (config: { loop: true }), динамически добавлять или удалять маркеры после загрузки, либо реализовать систему приоритетов, где новый эффект может прерывать воспроизведение текущего, если они используют один аудиоспрайт.
