О чем этот пример
В играх часто используются десятки небольших звуковых эффектов. Загружать каждый из них отдельным файлом неэффективно — это увеличивает количество HTTP-запросов и усложняет управление. В этой статье мы разберем пример из официальной документации Phaser, который демонстрирует мощный подход к работе со звуком: использование **Audio Sprites**. Вы научитесь загружать один аудиофайл, содержащий множество эффектов, и создавать интерактивный интерфейс для их воспроизведения. Этот метод не только оптимизирует загрузку ресурсов, но и делает код управления звуком чище и понятнее.
Версия 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.image('title', 'assets/pics/catastrophi.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.json('sfx', 'assets/audio/SoundEffects/fx_mixdown.json');
this.load.audio('sfx', [
'assets/audio/SoundEffects/fx_mixdown.ogg',
'assets/audio/SoundEffects/fx_mixdown.mp3'
]);
}
create ()
{
this.add.image(400, 300, 'title');
const spritemap = this.cache.json.get('sfx').spritemap;
let i = 0;
for (const spriteName in spritemap)
{
if (!spritemap.hasOwnProperty(spriteName))
{
continue;
}
this.makeButton.call(this, spriteName, 680, 115 + i * 40);
i++;
}
this.input.on('gameobjectover', (pointer, button) =>
{
this.setButtonFrame(button, 0);
});
this.input.on('gameobjectout', (pointer, button) =>
{
this.setButtonFrame(button, 1);
});
this.input.on('gameobjectdown', function (pointer, button)
{
this.sound.playAudioSprite('sfx', button.name);
this.setButtonFrame(button, 2);
}, this);
this.input.on('gameobjectup', (pointer, button) =>
{
this.setButtonFrame(button, 0);
});
}
makeButton (name, x, y)
{
const button = this.add.image(x, y, 'button', 1).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;
}
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);
Что такое Audio Sprite и зачем это нужно
Audio Sprite — это концепция, заимствованная из графики. Так же, как image sprite (спрайт-лист) объединяет множество изображений в один файл, audio sprite объединяет множество коротких звуков в один аудиофайл. К нему прилагается JSON-файл (спрайтмап), который описывает временные метки для каждого звукового фрагмента: его начало и длительность.
Phaser умеет загружать такую связку и воспроизводить отдельные фрагменты по их имени, указанному в спрайтмапе. Это избавляет от необходимости создавать отдельные объекты Audio или HTMLAudioElement для каждого звука.
В нашем примере загружается один файл fx_mixdown в двух форматах (OGG и MP3 для кросс-браузерности) и JSON-файл с его разметкой.
Загрузка ресурсов: аудио и разметка
В методе preload() происходит подготовка всех необходимых для сцены ресурсов. Ключевой момент — одновременная загрузка аудиофайла и JSON-файла с одинаковым ключом 'sfx'. Это позволяет Phaser связать их в единый актив — Audio Sprite.
this.load.json('sfx', 'assets/audio/SoundEffects/fx_mixdown.json');
this.load.audio('sfx', [
'assets/audio/SoundEffects/fx_mixdown.ogg',
'assets/audio/SoundEffects/fx_mixdown.mp3'
]);
Также загружаются кнопки в виде спрайтшита (три кадра для состояний: над, вне, нажата) и точечный шрифт для подписей. Обратите внимание на настройку pixelArt: true в конфиге игры — она отключает сглаживание при масштабировании, что критично для ретро-стиля.
Создание интерфейса из данных JSON
В методе create() мы получаем доступ к загруженной разметке (спрайтмапу) через кэш: this.cache.json.get('sfx').spritemap. Этот объект содержит имена звуковых эффектов как ключи.
const spritemap = this.cache.json.get('sfx').spritemap;
Далее в цикле для каждого имени эффекта из спрайтмапа создается кнопка с помощью метода makeButton(). Кнопке в свойство name присваивается имя эффекта. Это имя позже будет использовано для воспроизведения конкретного звука. Таким образом, интерфейс генерируется динамически на основе данных из JSON-файла. Добавить новый звук в аудиоспрайт и разметку — и он автоматически появится в интерфейсе.
Обработка событий ввода и воспроизведение звука
Интерактивность кнопкам добавляет setInteractive(). Далее сцена подписывается на события мыши для всех игровых объектов (в нашем случае — кнопок).
Ключевое событие — gameobjectdown (кнопка нажата). В его обработчике происходит воспроизведение звука.
this.sound.playAudioSprite('sfx', button.name);
Метод playAudioSprite() принимает два аргумента: ключ загруженного аудиоспрайта и имя конкретного звукового фрагмента из спрайтмапа. Phaser сам находит нужный отрезок в аудиопотоке и воспроизводит его.
Остальные события (gameobjectover, gameobjectout, gameobjectup) меняют кадр кнопки, создавая визуальный отклик. Метод setButtonFrame() вручную изменяет отображаемый кадр спрайтшита, используя button.scene.textures.getFrame().
Важная настройка: `audio.noAudio`
В конфигурации игры есть неочевидная, но важная настройка:
const config = {
// ... другие настройки
audio: {
noAudio: true
}
};
Установка noAudio: true не отключает звук в игре. Вместо этого она говорит Phaser использовать собственную, более легковесную реализацию аудиосистемы (NoAudioSoundManager), которая работает поверх тега <audio>. Это гарантирует совместимость в браузерах, где может быть недоступна полноценная Web Audio API (например, в некоторых мобильных браузерах или старых системах). Для Audio Sprites эта настройка работает корректно.
Что попробовать дальше
Audio Sprites в Phaser — это элегантное и производительное решение для управления множеством звуковых эффектов. Оно сокращает время загрузки, количество запросов к серверу и упрощает логику кода.
**Идеи для экспериментов:**
1. Добавьте слайдер для глобальной громкости всех эффектов, используя this.sound.volume.
2. Реализуйте кнопку mute, отслеживая состояние через this.sound.mute.
3. Попробуйте воспроизводить один и тот же эффект с разной скоростью (параметр rate в playAudioSprite) для создания вариативности, например, для шагов персонажа.
