О чем этот пример

Работа со множеством мелких звуковых эффектов (SFX) в игре может превратиться в хаос загрузок и вызовов. Phaser предлагает элегантное решение — Audio Sprites. Это технология, объединяющая множество коротких звуков в один аудиофайл с JSON-манифестом, подобно спрайт-листам для графики. В этой статье разберем пример, который демонстрирует, как загрузить аудиоспрайт, создать панель управления для каждого звукового клипа и воспроизводить их по нажатию. Этот подход оптимизирует загрузку, снижает количество HTTP-запросов и упрощает управление звуковыми ресурсами в вашем проекте.

Версия 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.audioSprite('sfx', 'assets/audio/SoundEffects/fx_mixdown.json', [ '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
};

const game = new Phaser.Game(config);

Загрузка аудиоспрайта и ресурсов

В методе preload() происходит загрузка всех необходимых ресурсов. Ключевой момент — использование this.load.audioSprite(). Этот метод загружает аудиоспрайт, который состоит из JSON-файла с разметкой (тайминги и названия звуков) и самих аудиофайлов в нескольких форматах для кроссбраузерной совместимости.

Изображение, spritesheet для кнопок и bitmap-шрифт загружаются для создания пользовательского интерфейса.

this.load.audioSprite('sfx', 'assets/audio/SoundEffects/fx_mixdown.json', [ 'assets/audio/SoundEffects/fx_mixdown.ogg', 'assets/audio/SoundEffects/fx_mixdown.mp3' ]);

Извлечение карты звуков и создание интерфейса

В create() мы получаем доступ к данным аудиоспрайта через кеш: this.cache.json.get('sfx').spritemap. Этот объект содержит имена всех звуковых фрагментов (спрайтов) и их параметры (время начала и длительность).

Далее в цикле для каждого имени звука создается интерактивная кнопка, используя вспомогательный метод makeButton. Важно, что свойству button.name присваивается имя звукового спрайта — это позволит later идентифицировать, какой звук нужно проиграть.

const spritemap = this.cache.json.get('sfx').spritemap;
for (const spriteName in spritemap) {
    this.makeButton.call(this, spriteName, 680, 115 + i * 40);
    i++;
}

Обработка взаимодействий с кнопкой

Для обработки наведения и кликов курсора на кнопки используются события ввода Phaser. События gameobjectover, gameobjectout, gameobjectup меняют кадр кнопки (ее визуальное состояние), создавая эффект интерактивности, через метод setButtonFrame.

Самое важное происходит в обработчике gameobjectdown. При нажатии на кнопку вызывается this.sound.playAudioSprite('sfx', button.name). Phaser по имени ключа ('sfx') находит загруженный аудиоспрайт, а по button.name (например, 'laser' или 'explosion') — точный временной отрезок в аудиофайле и воспроизводит его.

this.input.on('gameobjectdown', function (pointer, button) {
    this.sound.playAudioSprite('sfx', button.name);
    this.setButtonFrame(button, 2);
}, this);

Вспомогательные методы: makeButton и setButtonFrame

Метод makeButton создает интерактивное изображение-кнопку из загруженного spritesheet и подписывает ее bitmap-текстом. Увеличение масштаба (setScale) делает кнопку более удобной для нажатия.

Метод setButtonFrame изменяет отображаемый кадр кнопки. Он напрямую работает с кадром (frame) объекта Image, получая нужный кадр из текстуры. Это эффективный способ визуальной обратной связи.

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);
}

Что попробовать дальше

Audio Sprites — это мощный паттерн для управления звуковыми эффектами в Phaser. Он сокращает время загрузки, упрощает организацию кода и позволяет удобно работать с десятками звуков как с единым ресурсом. Для экспериментов попробуйте: создать свой аудиоспрайт из набора WAV-файлов с помощью инструментов вроде audiosprite; добавить кнопкам воспроизведение звука при наведении (gameobjectover); реализовать остановку звука или изменение его громкости в зависимости от игрового контекста.