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

Работа со звуковыми эффектами в играх часто превращается в управление десятками отдельных файлов. Audio Sprites в 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.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 — это аудиоаналог спрайт-листа. Несколько коротких звуковых эффектов (например, выстрелов, шагов, кликов) объединяются в один большой аудиофайл. Отдельный JSON-файл описывает временные метки (тайминги) для каждого эффекта внутри этого файла.

Это дает два ключевых преимущества: 1. **Оптимизация загрузки:** Браузеру нужно установить одно HTTP-соединение для загрузки одного аудиофайла вместо десятков. 2. **Удобство управления:** Вы можете воспроизводить эффекты по имени, как если бы переключали кадры у спрайта, без необходимости создавать отдельные объекты для каждого звука.

Подготовка данных: Загрузка аудио и 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'
]);

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

Создание интерфейса: Кнопки из данных JSON

В create() мы извлекаем карту спрайтов из кэша и динамически создаем кнопки для каждого звукового эффекта. Это демонстрирует, как можно автоматически построить UI на основе данных.

const spritemap = this.cache.json.get('sfx').spritemap;

let i = 0;
for (const spriteName in spritemap)
{
    this.makeButton.call(this, spriteName, 680, 115 + i * 40);
    i++;
}

Метод makeButton создает интерактивное изображение (this.add.image) и подписывает его Bitmap-текстом с именем эффекта. Имя эффекта сохраняется в свойстве button.name — это критически важно для последующего воспроизведения.

Воспроизведение эффекта и обработка ввода

Логика воспроизведения срабатывает по клику на кнопку. Обработчик события gameobjectdown использует метод this.sound.playAudioSprite(), передавая ему ключ аудиоресурса и имя спрайта (которое мы сохранили в button.name).

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

Остальные обработчики (gameobjectover, gameobjectout, gameobjectup) меняют кадр кнопки для визуальной обратной связи, используя вспомогательный метод setButtonFrame.

Важная настройка конфигурации

Обратите внимание на объект audio в конфиге игры. Параметр noAudio: true указывает Phaser использовать HTML5 Audio вместо Web Audio API. Это может быть полезно для специфичных кейсов или кросс-доменной загрузки аудио, но обычно для игр предпочтительнее Web Audio API (значение по умолчанию), который предлагает больше контроля и лучшее быстродействие.

const config = {
    // ... другие настройки
    audio: {
        noAudio: true
    }
};

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

Audio Sprites — это мощный паттерн для управления звуковыми эффектами в Phaser, который повышает эффективность загрузки и чистоту кода. Для экспериментов попробуйте

  1. Создать свой аудио-спрайт с помощью инструментов вроде audiosprite
  2. Добавить контроль громкости или панорамирования для отдельных эффектов через параметры метода playAudioSprite
  3. Реализовать кэширование часто используемых эффектов для мгновенного повторного воспроизведения