О чем этот пример
В играх часто требуется воспроизвести не весь большой аудиофайл, а только конкретный звук из него, например, выстрел или прыжок. Загрузка десятков мелких файлов неэффективна. Phaser позволяет решить эту проблему с помощью аудио-маркеров — меток внутри одного файла, которые указывают, какой фрагмент и когда проигрывать. В этой статье мы разберем готовый пример и научимся создавать интерактивную панель для тестирования звуковых эффектов, загруженных из одного файла.
Версия 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);
Подготовка аудиофайла и загрузка ресурсов
Ключевая идея — использовать один аудиофайл, содержащий все звуковые эффекты, и набор маркеров, которые описывают временные отрезки для каждого эффекта.
В методе preload мы загружаем аудиофайл с ключом 'sfx'. Обратите внимание на опцию instances: 4. Она указывает, сколько независимых экземпляров этого звука можно воспроизводить одновременно. Это полезно для быстрых повторяющихся звуков, например, выстрелов.
this.load.audio('sfx', [
'assets/audio/SoundEffects/fx_mixdown.ogg',
'assets/audio/SoundEffects/fx_mixdown.mp3'
], {
instances: 4
});
Маркеры определяются как массив объектов в свойстве класса. Каждый маркер содержит имя, время начала (start в секундах) и продолжительность (duration в секундах). Конфигурация config в примере пуста, но может использоваться для дополнительных настроек звука.
markers = [
{ name: 'alien death', start: 1, duration: 1.0, config: {} },
// ... другие маркеры
];
Создание интерактивных кнопок для каждого маркера
Для удобного тестирования звуков в примере создаются кнопки — по одной для каждого маркера. Это происходит в методе create.
Цикл проходит по массиву маркеров и для каждого вызывает метод makeButton, передавая имя эффекта и его индекс в массиве.
for (let i = 0; i < this.markers.length; i++)
{
this.makeButton.call(this, this.markers[i].name, i);
}
Метод makeButton создает спрайт кнопки из загруженного спрайтшита и делает его интерактивным с помощью setInteractive(). Индекс маркера сохраняется в данных объекта кнопки через setData(). Это позволит позже понять, какую кнопку нажали. Рядом с кнопкой создается текст с именем эффекта, используя битмап-шрифт.
makeButton (name, index)
{
const button = this.add.image(680, 115 + index * 40, 'button', 1).setInteractive();
button.setData('index', index);
// ... создание текста
}
Обработка событий ввода и воспроизведение звука
Интерактивность кнопок обеспечивается обработчиками событий, которые навешиваются на входную систему Phaser (this.input.on). В примере обрабатываются наведение, уход курсора, нажатие и отпускание кнопки мыши.
Самое важное происходит в обработчике gameobjectdown (когда кнопку нажали). Из данных кнопки извлекается сохраненный индекс. Затем вызывается this.sound.play(). Первый аргумент — ключ загруженного звука ('sfx'), второй — объект маркера из массива, найденный по этому индексу.
this.input.on('gameobjectdown', function (pointer, button)
{
const index = button.getData('index');
this.sound.play('sfx', this.markers[index]);
this.setButtonFrame(button, 2);
}, this);
Phaser, получив объект маркера, проиграет не весь файл, а только его фрагмент, начиная с start и продолжительностью duration. Благодаря предзагрузке с несколькими instances, эффекты могут накладываться друг на друга, если нажимать кнопки быстро.
Вспомогательный метод setButtonFrame меняет кадр спрайтшита кнопки, обеспечивая визуальный feedback при наведении и нажатии.
setButtonFrame (button, frame)
{
button.frame = button.scene.textures.getFrame('button', frame);
}
Особенности конфигурации: HTML5 Audio вместо Web Audio
Обратите внимание на конфигурацию игры. В ней явно отключен Web Audio API и используется стандартный HTML5 Audio.
const config = {
// ... другие настройки
audio: {
disableWebAudio: true
}
};
Это может быть необходимо для совместимости в некоторых браузерах или мобильных устройствах. Важно знать, что поведение и возможности API (например, точность маркеров или частота одновременного воспроизведения) могут различаться в зависимости от выбранного аудио-бэкенда.
Что попробовать дальше
Использование маркеров — это мощный и экономичный способ управления звуковыми эффектами в Phaser. Он позволяет сократить количество HTTP-запросов и упростить организацию аудио-контента. Для экспериментов попробуйте создать свой набор маркеров в длинном аудиофайле, изменить количество instances при загрузке, чтобы увидеть разницу в наложении звуков, или настроить параметры в объекте config каждого маркера (например, громкость или петлю).
