О чем этот пример
Качественное звуковое сопровождение — ключевой элемент игровой атмосферы. Phaser предоставляет мощный, но простой API для работы с аудио, включая возможность разметки одного аудиофайла на отдельные эффекты (маркеры) и контроль над их воспроизведением. Эта статья разберет практический пример, демонстрирующий, как создавать интерактивные кнопки для проигрывания конкретных звуков из аудиоспрайта, а также как реализовать глобальные управление паузой и возобновлением всего звукового потока. Этот подход экономит ресурсы и упрощает организацию звуков в проекте.
Версия 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);
Подготовка аудиоресурсов и создание маркеров
В методе preload() загружаются все необходимые ресурсы: фон, спрайт лист для кнопок, bitmap-шрифт и аудиофайл. Обратите внимание, что аудиофайл sfx загружается в двух форматах (OGG и MP3) для кроссбраузерной совместимости.
Ключевой элемент — массив markers. Каждый маркер описывает фрагмент внутри одного большого аудиофайла. Параметры name, start (начало в секундах) и duration (длительность в секундах) позволяют точно определить границы каждого звукового эффекта, такого как 'fireball' или 'spell'.
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: {} }
];
После загрузки аудио в create() создается объект звука this.fx. Затем для каждого описания в массиве markers вызывается метод this.fx.addMarker(). Это регистрирует маркеры внутри объекта Sound, после чего их можно проигрывать по имени, используя метод play().
Создание интерактивного интерфейса
Для каждого маркера создается кнопка вызова. Функция makeButton() создает интерактивное изображение (Image) из спрайт-листа и подписывает его bitmap-текстом. Важно, что свойству name кнопки присваивается имя маркера. Это позволит позднее идентифицировать, какую кнопку нажал игрок.
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;
}
Отдельно создается кнопка pauseResumeButton для глобального управления воспроизведением. Её имя задано как 'pause', что отличает её от кнопок маркеров. Взаимодействие со всеми кнопками обрабатывается едиными обработчиками событий ввода, что делает код чище и эффективнее.
Обработка ввода и логика воспроизведения
Вся логика реакции на нажатия кнопок сосредоточена в обработчиках событий gameobjectdown и gameobjectup. События gameobjectover и gameobjectout отвечают за визуальную обратную связь (смену кадра кнопки при наведении).
Когда нажата кнопка маркера, вызывается this.fx.play(button.name). Phaser ищет зарегистрированный маркер с таким именем и проигрывает соответствующий аудиофрагмент.
this.input.on('gameobjectdown', (pointer, button) =>
{
if (button.name === 'pause')
{
// Логика паузы/возобновления
}
else
{
// Воспроизвести маркер по имени кнопки
this.fx.play(button.name);
this.setButtonFrame(button, 2);
}
});
Для кнопки 'pause' логика проверяет состояние звукового объекта. Свойства isPaused и isPlaying объекта this.fx позволяют определить текущее состояние и вызвать соответствующий метод: pause() или resume().
Управление состоянием и отображение кнопки паузы
Актуальное состояние звука (играет, на паузе, остановлен) должно отображаться на интерфейсе. Это делается в методе update(), который вызывается каждый кадр.
Здесь проверяются флаги this.fx.isPaused и this.fx.isPlaying, и в зависимости от них обновляется текст на кнопке глобального управления. Также выполняется выравнивание текста по центру кнопки.
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;
}
Такой подход гарантирует, что игрок всегда видит актуальную команду, которую выполнит кнопка при следующем нажатии.
Важная деталь конфигурации: отключение Web Audio
В конфигурации игры есть неочевидный, но важный параметр. Он заставляет Phaser использовать HTML5 Audio API вместо Web Audio API по умолчанию.
const config = {
// ... другие настройки ...
audio: {
disableWebAudio: true
}
};
Это может быть полезно для специфических случаев совместимости или отладки. В обычных условиях для современных браузеров рекомендуется использовать более мощный Web Audio API (значение по умолчанию false). Все рассмотренные методы (addMarker, play, pause, resume) работают одинаково в обоих режимах.
Что попробовать дальше
Использование аудиомаркеров в Phaser — это эффективный способ организовать звуковые эффекты игры, упаковав их в один файл. Механизмы pause() и resume() дают полный контроль над воспроизведением. Для экспериментов попробуйте: добавить больше маркеров с разными эффектами, реализовать остановку конкретного маркера (stop()), изменить громкость или скорость воспроизведения через config в маркере или динамически с помощью методов объекта Sound.
