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

В мобильной и веб-разработке производительность часто упирается в размер и скорость загрузки графики. Использование сжатых текстур (ASTC, ETC2, PVRTC, S3TC) — ключевой метод для оптимизации игр, особенно под платформы с ограниченной пропускной способностью или памятью. Этот пример демонстрирует, как Phaser 3 через загрузчик `pack` позволяет гибко и элегантно загружать одну и ту же текстуру в разных форматах сжатия, автоматически выбирая подходящий для текущего браузера или устройства. Это избавляет разработчика от необходимости писать сложные условия и вручную проверять поддержку кодеков.

Версия Phaser: код и демо в этой статье рассчитаны на Phaser 3.90.0.

Живой запуск

Ниже встроен рабочий билд примера. Оригинальный источник: GitHub.

Исходный код


var FilePackObject = {
    "pack": {
        "path": "assets/compressed",
        "files": [
            {
                "type": "texture",
                "key": "labs",
                "url": {
                    "ASTC": { type: "PVR", textureURL: 'compressed/textures-ASTC-4x4-lRGB.pvr', atlasURL: 'uncompressed/textures.json' },
                    "ETC": { type: "PVR", textureURL: 'compressed/textures-ETC2-lRGB.pvr', atlasURL: 'uncompressed/textures.json' },
                    "PVRTC": { type: "PVR", textureURL: 'compressed/textures-PVRTC-4BPP-lRGB.pvr', atlasURL: 'uncompressed/textures.json' },
                    "S3TC": { type: "PVR", textureURL: 'compressed/textures-S3TC-BC3-lRGB.pvr', atlasURL: 'uncompressed/textures.json' },
                    "IMG": { textureURL: 'textures.png', atlasURL: 'uncompressed/textures.json' }
                }
            }
        ]
    },
    "meta": {
        "generated": "1401380327373",
        "app": "Phaser 3 Asset Packer",
        "url": "https://phaser.io",
        "version": "1.0",
        "copyright": "Photon Storm Ltd. 2021"
    }
};

class Demo extends Phaser.Scene
{
    constructor ()
    {
        super();
    }

    preload ()
    {
        this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
        this.load.pack('pack1', FilePackObject);
    }

    create ()
    {
        const bubble = this.add.sprite(0, 40, 'labs', 'bubble256').setOrigin(0);
        const logo = this.add.sprite(80, 100, 'labs', 'logo').setOrigin(0);

        this.add.text(400, 570, logo.frame.source.compressionAlgorithm).setOrigin(0.5, 0);
    }
}

const config = {
    type: Phaser.AUTO,
    parent: 'phaser-example',
    width: 800,
    height: 600,
    backgroundColor: '#2d2d6d',
    scene: Demo
};

const game = new Phaser.Game(config);

Структура объекта FilePackObject

Вся конфигурация ассетов упакована в объект FilePackObject. Это JSON-структура, которую понимает загрузчик this.load.pack(). Она состоит из двух основных частей: pack и meta.

Объект pack содержит путь (path) и массив файлов (files). В данном случае загружается всего один ассет типа "texture" с ключом "labs". Самое важное — это свойство url, которое представляет собой объект с ключами для разных форматов сжатия ("ASTC", "ETC", "PVRTC", "S3TC") и резервного формата "IMG" (обычный PNG).

{
    "type": "texture",
    "key": "labs",
    "url": {
        "ASTC": { type: "PVR", textureURL: 'compressed/textures-ASTC-4x4-lRGB.pvr', atlasURL: 'uncompressed/textures.json' },
        "ETC": { type: "PVR", textureURL: 'compressed/textures-ETC2-lRGB.pvr', atlasURL: 'uncompressed/textures.json' },
        "PVRTC": { type: "PVR", textureURL: 'compressed/textures-PVRTC-4BPP-lRGB.pvr', atlasURL: 'uncompressed/textures.json' },
        "S3TC": { type: "PVR", textureURL: 'compressed/textures-S3TC-BC3-lRGB.pvr', atlasURL: 'uncompressed/textures.json' },
        "IMG": { textureURL: 'textures.png', atlasURL: 'uncompressed/textures.json' }
    }
}

Каждый формат содержит два URL: textureURL (путь к сжатому файлу изображения, например, .pvr) и atlasURL (путь к JSON-файлу атласа, описывающему кадры). Для форматов сжатия также указан type: "PVR", что сообщает загрузчику, как обрабатывать файл. Формат "IMG" не имеет типа, что подразумевает загрузку обычного PNG-изображения. Phaser автоматически проверит поддержку браузером каждого формата и загрузит первый подходящий, начиная с самого эффективного.

Настройка загрузчика и загрузка пака

В методе preload() сцены Demo происходит настройка базового URL и непосредственная загрузка пака ассетов.

preload ()
{
    this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
    this.load.pack('pack1', FilePackObject);
}

Сначала this.load.setBaseURL() задаёт корневой путь для всех последующих загрузок. Это удобно, чтобы не писать полные URL в каждом описании ассета.

Затем вызывается this.load.pack('pack1', FilePackObject). Первый аргумент — это произвольный ключ для пакета (может быть полезен, если пакетов несколько). Второй аргумент — наш объект конфигурации. Загрузчик Phaser проанализирует объект FilePackObject, проверит поддержку форматов сжатия текстуры в текущей среде выполнения и выберет оптимальный вариант. Если ни один из форматов сжатия (ASTC, ETC2 и т.д.) не поддерживается, будет использован резервный вариант "IMG" с PNG-изображением. Вся эта логика выполняется "под капотом", скрывая от разработчика сложности кроссплатформенной совместимости.

Создание спрайтов и вывод информации

После успешной загрузки в методе create() мы можем использовать текстуру с ключом 'labs', как если бы это была обычная текстура.

create ()
{
    const bubble = this.add.sprite(0, 40, 'labs', 'bubble256').setOrigin(0);
    const logo = this.add.sprite(80, 100, 'labs', 'logo').setOrigin(0);
    this.add.text(400, 570, logo.frame.source.compressionAlgorithm).setOrigin(0.5, 0);
}

Первые две строки создают спрайты из загруженного атласа. this.add.sprite(x, y, 'labs', 'bubble256') создаёт спрайт, использующий текстуру с ключом 'labs' и конкретный кадр (frame) из атласа с именем 'bubble256'. Аналогично создаётся спрайт с кадром 'logo'. Метод .setOrigin(0) устанавливает точку привязки спрайта в его левый верхний угол.

Самая интересная часть — последняя строка: this.add.text(... logo.frame.source.compressionAlgorithm ...). Здесь мы получаем доступ к объекту кадра (logo.frame), его источнику (source — это сама текстура) и свойству compressionAlgorithm. Phaser автоматически записывает в это свойство строку с названием алгоритма сжатия, который был фактически использован при загрузке текстуры (например, "ASTC", "ETC", "PVRTC" или "NONE" для несжатого PNG). Этот текст выводится на экран, что позволяет в реальном времени видеть, какой формат сработал в вашем браузере.

Конфигурация игры и запуск

Стандартная конфигурация игры Phaser указывает на нашу сцену Demo.

const config = {
    type: Phaser.AUTO,
    parent: 'phaser-example',
    width: 800,
    height: 600,
    backgroundColor: '#2d2d6d',
    scene: Demo
};

const game = new Phaser.Game(config);

Здесь нет ничего специфичного для загрузки сжатых текстур. Конфигурация обычная: задаётся тип рендерера (Phaser.AUTO), родительский DOM-элемент (parent), размеры холста, цвет фона и основная сцена. Важно, что вся магия с выбором формата текстуры происходит на этапе preload(), до создания игровых объектов. После создания экземпляра new Phaser.Game(config) запускается жизненный цикл сцены: init(), preload(), create(), update().

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

Пример наглядно показывает мощь системы загрузки Phaser 3 для работы с оптимизированными активами. Разработчик описывает все возможные варианты текстуры в одном месте, а движок сам выбирает лучший из поддерживаемых. Для экспериментов попробуйте

  1. Добавить свои форматы сжатия (например, проверьте поддержку "ASTC" в разных браузерах)
  2. Создать несколько текстур в пакете с разными ключами
  3. Использовать эту технику для загрузки не только текстур, но и других типов ассетов, поддерживаемых методом pack
  4. Вручную отключить поддержку современных кодеков в браузере для тестирования резервного PNG-варианта