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

Управление множеством звуковых эффектов — частая задача в геймдеве. Загружать каждый файл отдельно неудобно и может замедлить загрузку игры. В этой статье мы разберем пример из официальной документации Phaser, который демонстрирует мощный инструмент — аудиоспрайты. Вы научитесь объединять несколько звуков в один файл и управлять ими по имени, что упростит код и оптимизирует производительность вашего проекта.

Версия 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);

Что такое аудиоспрайт и зачем он нужен

Аудиоспрайт (Audio Sprite) — это концепция, похожая на графические спрайтшиты. Несколько коротких звуковых эффектов объединяются в один большой аудиофайл, а отдельный JSON-файл описывает временные метки для каждого звука (его начало и длительность).

Это дает два ключевых преимущества: 1. **Снижение количества HTTP-запросов:** Вместо десятков отдельных файлов загружаются всего два (аудио и JSON). Это ускоряет загрузку игры. 2. **Удобство управления:** В коде вы обращаетесь к звукам по их логическому имени (например, 'laser' или 'explosion'), а не по имени файла. Phaser сам «вырезает» нужный фрагмент из общего файла.

В примере используется файл fx_mixdown.json, который содержит описание спрайтшита, и соответствующие ему аудиофайлы в форматах OGG и MP3 для кросс-браузерной совместимости.

Загрузка ресурсов: ключевой метод `load.audioSprite`

Основная магия происходит на этапе предзагрузки в методе preload. Вместо загрузки кучи файлов используется один вызов.

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

Разберем аргументы: 1. 'sfx' — это ключ, по которому аудиоспрайт будет доступен в кэше игры. 2. Второй аргумент — путь к JSON-файлу с разметкой. 3. Третий аргумент — массив путей к самим аудиофайлам. Phaser автоматически выберет подходящий формат, который поддерживает браузер пользователя (OGG часто для Firefox и Chrome, MP3 для Safari).

После загрузки данные из JSON можно получить через кэш: this.cache.json.get('sfx').

Динамическое создание интерфейса из данных спрайта

В методе create происходит чтение данных аудиоспрайта и создание на их основе интерфейса — кнопок для воспроизведения каждого звука.

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

Код получает объект spritemap из загруженного JSON. Этот объект содержит свойства, где ключ — это имя звука (например, 'saw'), а значение — его параметры (start, end).

Цикл for...in проходит по всем именам звуков в spritemap. Для каждого имени вызывается метод makeButton, который создает интерактивную кнопку. Позиция кнопки по вертикали (`y) увеличивается с каждой итерацией (i * 40`), что выстраивает кнопки в столбик.

Воспроизведение звука и обработка событий кнопки

Сердце примера — обработка событий ввода. Кнопки реагируют на наведение и клик, а при нажатии проигрывается соответствующий звук.

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

Обратите внимание на ключевой метод this.sound.playAudioSprite. Он принимает два аргумента: 1. Ключ аудиоспрайта ('sfx'), который был задан при загрузке. 2. Имя конкретного звука (button.name), которое было присвоено кнопке при создании и соответствует ключу в spritemap.

Phaser сам находит в общем аудиофайле нужный отрезок времени и воспроизводит его. Это изящно скрывает всю внутреннюю сложность от разработчика.

Метод setButtonFrame меняет кадр спрайтшита кнопки, чтобы визуально показать ее состояние (обычная, наведенная, нажатая).

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

Аудиоспрайты в Phaser — это элегантное и производительное решение для управления звуковыми эффектами. Они позволяют держать код чистым, а ресурсы — организованными. Для экспериментов попробуйте: создать свой аудиоспрайт с помощью инструментов вроде audiosprite или подобных; добавить настройку громкости или панорамирования для отдельных звуков в спрайте; привязать воспроизведение не к клику мыши, а к игровым событиям, например, столкновению или сбору предмета.