О чем этот пример
Когда в игре много коротких звуковых эффектов, загружать каждый по отдельному файлу неэффективно. Это увеличивает количество HTTP-запросов и усложняет управление ассетами. Пример из официальной документации Phaser демонстрирует мощный подход: использование одного аудиофайла с маркерами. Вы загружаете один файл со всей коллекцией звуков, а затем воспроизводите конкретные фрагменты по имени и временным меткам. Это идеально подходит для систем выстрелов, шагов, голосовых реплик или UI-кликов, где важна скорость отклика и порядок загрузки.
Версия Phaser: код и демо в этой статье рассчитаны на Phaser 3.90.0.
Живой запуск
Ниже встроен рабочий билд примера. Оригинальный источник: GitHub.
Исходный код
class Example extends Phaser.Scene
{
markers = [
{ name: 'alien death', start: 1, duration: 1.0, config: {} },
{ name: 'boss hit', start: 3, duration: 0.5, config: {} },
{ name: 'escape', start: 4, duration: 3.2, config: {} },
{ name: 'meow', start: 8, duration: 0.5, config: {} },
{ name: 'numkey', start: 9, duration: 0.1, config: {} },
{ name: 'ping', start: 10, duration: 1.0, config: {} },
{ name: 'death', start: 12, duration: 4.2, config: {} },
{ name: 'shot', start: 17, duration: 1.0, config: {} },
{ name: 'squit', start: 19, duration: 0.3, config: {} }
];
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.audio('sfx', [
'assets/audio/SoundEffects/fx_mixdown.ogg',
'assets/audio/SoundEffects/fx_mixdown.mp3'
], {
instances: 4
});
}
create ()
{
this.add.image(400, 300, 'title');
for (let i = 0; i < this.markers.length; i++)
{
this.makeButton.call(this, this.markers[i].name, 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)
{
const index = button.getData('index');
this.sound.play('sfx', this.markers[index]);
this.setButtonFrame(button, 2);
}, this);
this.input.on('gameobjectup', (pointer, button) =>
{
this.setButtonFrame(button, 0);
});
}
makeButton (name, index)
{
const button = this.add.image(680, 115 + index * 40, 'button', 1).setInteractive();
button.setData('index', index);
button.setScale(2, 1.5);
const text = this.add.bitmapText(button.x - 40, button.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: {
disableWebAudio: true
}
};
const game = new Phaser.Game(config);
Подготовка данных: Массив маркеров
Вся логика работы со звуками начинается с определения массива маркеров. Каждый маркер — это объект, описывающий фрагмент внутри большого аудиофайла.
markers = [
{ name: 'alien death', start: 1, duration: 1.0, config: {} },
{ name: 'boss hit', start: 3, duration: 0.5, config: {} },
// ... другие маркеры
];
Ключевые свойства:
- name: Уникальное имя для обращения к звуку.
- start: Время начала фрагмента в секундах.
- duration: Длительность фрагмента в секундах.
- config: Дополнительные опции для экземпляра звука (громкость, затухание и т.д.).
Такой подход централизует управление таймингом всех эффектов в одном месте кода.
Загрузка аудио с несколькими экземплярами
Чтобы звуковые эффекты могли накладываться друг на друга (например, выстрел во время взрыва), необходимо загрузить аудио с параметром instances. Это создаст пул из нескольких независимых экземпляров одного и того же аудиофайла.
this.load.audio('sfx', [
'assets/audio/SoundEffects/fx_mixdown.ogg',
'assets/audio/SoundEffects/fx_mixdown.mp3'
], {
instances: 4
});
Здесь 'sfx' — это ключ аудио. Указание форматов .ogg и .mp3 обеспечивает кроссбраузерность. Параметр instances: 4 означает, что Phaser создаст 4 экземпляра звука, что позволяет одновременно воспроизводить до 4 эффектов. Если все экземпляры заняты, новый звук не проиграется, поэтому число нужно подбирать под потребности игры.
Также в конфигурации игры используется audio: { disableWebAudio: true }, что заставляет Phaser использовать HTML5 Audio. Это может быть нужно для специфичных случаев совместимости.
Создание интерактивных кнопок
Для демонстрации каждый звуковой маркер привязан к кнопке в интерфейсе. Метод makeButton создает изображение-кнопку и текстовую метку.
makeButton (name, index)
{
const button = this.add.image(680, 115 + index * 40, 'button', 1).setInteractive();
button.setData('index', index);
button.setScale(2, 1.5);
const text = this.add.bitmapText(button.x - 40, button.y - 8, 'nokia', name, 16);
text.x += (button.width - text.width) / 2;
}
Ключевые моменты:
- setInteractive() делает изображение кликабельным.
- setData('index', index) сохраняет индекс маркера из массива прямо в объекте кнопки. Это удобный способ связать данные с игровым объектом.
- bitmapText используется для отображения пиксельного шрифта.
- Позиция кнопки рассчитывается по формуле 115 + index * 40, что выстраивает их вертикальный список.
Воспроизведение звука по маркеру
Основное действие происходит в обработчике события gameobjectdown (нажатие на кнопку).
this.input.on('gameobjectdown', function (pointer, button)
{
const index = button.getData('index');
this.sound.play('sfx', this.markers[index]);
this.setButtonFrame(button, 2);
}, this);
Алгоритм:
1. Из кнопки извлекается сохраненный ранее индекс (getData('index')).
2. Вызывается this.sound.play(). Первый аргумент — ключ аудио ('sfx'), второй — объект маркера (this.markers[index]).
3. Phaser находит свободный экземпляр звука 'sfx' и воспроизводит фрагмент, начиная со времени start и длительностью duration, указанными в маркере.
4. Метод setButtonFrame меняет кадр спрайта кнопки для визуальной обратной связи.
Остальные обработчики (gameobjectover, out, up) отвечают за анимацию наведения и отпускания кнопки, меняя ее спрайт-фрейм.
Что попробовать дальше
Использование маркеров в аудиосистеме Phaser — это профессиональный подход к управлению звуковыми эффектами. Он оптимизирует загрузку, упрощает организацию кода и позволяет гибко управлять воспроизведением. Для экспериментов попробуйте: изменить количество instances и посмотреть, что происходит при одновременном нажатии многих кнопок; добавить в config маркера параметры volume или detune; или динамически создавать маркеры на основе данных из JSON-файла.
