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

В играх часто требуется множество коротких звуковых эффектов, таких как выстрелы, прыжки или клики по кнопкам. Загрузка каждого эффекта отдельным файлом может быть неэффективной и усложнять управление. Пример демонстрирует, как использовать 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 и ресурсов интерфейса

В методе preload() загружаются все необходимые ресурсы для примера. Ключевой момент — загрузка двух связанных файлов: JSON с метаданными и аудиофайла, содержащего все звуки. Также загружаются изображения и шрифты для создания кнопок.

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'
]);

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

Создание динамических кнопок на основе метаданных звуков

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

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

Функция makeButton создает интерактивное изображение кнопки, задает ей имя (spriteName) и добавляет текстовую метку с этим именем. Важно: имя звукового спрайта сохраняется в свойстве button.name — оно будет использоваться как идентификатор для воспроизведения.

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

Код настраивает обработчики событий для взаимодействия с кнопками. При наведении, уходе курсора, нажатии и отпускании меняется кадр кнопки, создавая визуальный отклик. Самое важное происходит в событии gameobjectdown.

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

Метод this.sound.playAudioSprite() принимает два аргумента: ключ аудиоресурса ('sfx') и имя конкретного звукового спрайта (button.name). Phaser использует загруженные метаданные из JSON, чтобы вырезать и воспроизвести именно нужный сегмент из общего аудиофайла. Обратите внимание на использование function и контекста this для корректного обращения к менеджеру звуков сцены.

Важная конфигурация: режим без аудио

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

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

При установке noAudio: true вся звуковая система Phaser отключается. Вызовы методов, таких как playAudioSprite, будут безопасно игнорироваться, а не вызывать ошибки. Это позволяет легко тестировать геймплей, не зависящий от звука, или создавать сборки для платформ с ограничениями.

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

Audio Sprites в Phaser предлагают чистый и производительный способ работы со множеством звуковых эффектов. Вместо управления десятками отдельных файлов вы контролируете один аудиофайл и JSON-манифест. Для экспериментов попробуйте: создать свой Audio Sprite с помощью инструментов вроде Audiosprite или аналогичных; добавить параметры к playAudioSprite, такие как громкость или скорость воспроизведения; или использовать разные Audio Sprites для категорий звуков (интерфейс, оружие, окружение) для более тонкого управления.