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

При работе с Texture Atlas в Phaser каждый фрейм — это не просто прямоугольник с картинкой. В него можно загрузить пользовательские данные, которые затем использовать в игре. В этом примере показано, как хранить предустановленный цвет тинтинга (`tint`) прямо в JSON-описании атласа и применять его к спрайтам при создании. Этот подход позволяет дизайнерам задавать визуальные параметры прямо в данных атласа, делая контент более самодостаточным и удобным для итераций.

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

Живой запуск

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

Исходный код


class Example extends Phaser.Scene
{
    atlasJSON = {
        frames: [
            {
                filename: 'contra2',
                frame: {x: 2,y: 316,w: 142,h: 222},
                rotated: false,
                trimmed: false,
                spriteSourceSize: {x: 0,y: 0,w: 142,h: 222},
                sourceSize: {w: 142,h: 222},
                pivot: {x: 0.5,y: 0.5},
                tint: 0xff0000
            },
            {
                filename: 'exocet_spaceman',
                frame: {x: 146,y: 316,w: 153,h: 175},
                rotated: false,
                trimmed: false,
                spriteSourceSize: {x: 0,y: 0,w: 153,h: 175},
                sourceSize: {w: 153,h: 175},
                pivot: {x: 0.5,y: 0.5},
                tint: 0x00ff00
            },
            {
                filename: 'contra3',
                frame: {x: 645,y: 197,w: 246,h: 201},
                rotated: false,
                trimmed: false,
                spriteSourceSize: {x: 0,y: 0,w: 246,h: 201},
                sourceSize: {w: 246,h: 201},
                pivot: {x: 0.5,y: 0.5},
                tint: 0xff00ff
            },
            {
                filename: 'shocktroopers-lulu2',
                frame: {x: 301,y: 337,w: 133,h: 188},
                rotated: false,
                trimmed: false,
                spriteSourceSize: {x: 0,y: 0,w: 133,h: 188},
                sourceSize: {w: 133,h: 188},
                pivot: {x: 0.5,y: 0.5},
                tint: 0x0000ff
            } ],
        meta: {
            app: 'http://www.codeandweb.com/texturepacker',
            version: '1.0',
            image: 'megaset-3.png',
            format: 'RGBA8888',
            size: {w: 1023,h: 691},
            scale: '1',
            smartupdate: '$TexturePacker:SmartUpdate:5e8f90752cfd57d3adfb39bcd3eef1b6:87d98cec6fa616080f731b87726d6a1e:b55588eba103b49b35a0a59665ed84fd
#39; } }; preload () { this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/'); this.load.atlas('megaset', 'assets/atlas/megaset-3.png', this.atlasJSON); } create () { const atlasTexture = this.textures.get('megaset'); const frames = atlasTexture.getFrameNames(); let x = 100; for (let i = 0; i < frames.length; i++) { const img = this.add.image(x, 300, 'megaset', frames[i]); x += 200; // The frames have custom data stored in them (see the JSON below) which we can use to tint them: img.setTint(img.frame.customData.tint); } } } const config = { type: Phaser.WEBGL, width: 800, height: 600, parent: 'phaser-example', scene: Example }; const game = new Phaser.Game(config);

Структура кастомных данных в атласе

Ключевая идея в том, что объект, описывающий фрейм в JSON-атласе, может содержать любые пользовательские свойства помимо стандартных (frame, rotated и т.д.). Phaser загружает эти свойства и делает их доступными.

В предоставленном примере для каждого фрейма добавлено свойство tint с шестнадцатеричным значением цвета (например, 0xff0000 для красного). Это свойство не является частью стандартного формата Texture Packer — оно добавлено разработчиком вручную. После загрузки атласа Phaser сохраняет весь объект фрейма, включая это кастомное свойство, в текстуру.

{
    filename: 'contra2',
    frame: {x: 2, y: 316, w: 142, h: 222},
    // ... другие стандартные свойства ...
    pivot: {x: 0.5, y: 0.5},
    tint: 0xff0000 // Пользовательское свойство
}

Загрузка атласа с пользовательским JSON

Для загрузки используется метод this.load.atlas. Важный момент: вместо указания пути к JSON-файлу, мы передаём сам JavaScript-объект с данными атласа (this.atlasJSON). Это позволяет динамически генерировать или модифицировать данные атласа перед загрузкой, что открывает возможности для процедурной генерации.

preload ()
{
    this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
    this.load.atlas('megaset', 'assets/atlas/megaset-3.png', this.atlasJSON);
}

После выполнения этого кода в кэше текстур появится текстура с ключом 'megaset', содержащая все фреймы и их пользовательские данные.

Доступ к фреймам и их пользовательским данным

В методе create мы получаем ссылку на загруженную текстуру атласа. Метод getFrameNames() возвращает массив имён всех фреймов. Далее в цикле создаются изображения, и для каждого из них извлекается кастомное свойство tint.

create ()
{
    const atlasTexture = this.textures.get('megaset');
    const frames = atlasTexture.getFrameNames();
    let x = 100;

    for (let i = 0; i < frames.length; i++)
    {
        const img = this.add.image(x, 300, 'megaset', frames[i]);
        x += 200;
        // Доступ к пользовательским данным фрейма
        img.setTint(img.frame.customData.tint);
    }
}

Обратите внимание на путь: img.frame.customData.tint. Свойство frame объекта Image ссылается на объект фрейма в текстуре. Все нестандартные свойства исходного JSON попадают в объект customData этого фрейма. Таким образом, мы можем прочитать заранее заданный цвет и применить его методом setTint.

Практическое применение и расширение идеи

Типтинг — лишь простой пример. В свойстве customData можно хранить любую информацию, полезную для игровой логики:

*   **Смещение коллайдера:** `{ colliderOffset: { x: 0, y: 10 } }`.
*   **Сила атаки или тип урона для спрайта оружия:** `{ attackPower: 15, damageType: 'fire' }`.
*   **Имя анимации для автоматического проигрывания:** `{ idleAnim: 'hero_idle' }`.
*   **Звуковые эффекты:** `{ sfxKey: 'jump_sound' }`.

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

// Пример чтения других кастомных данных
if (img.frame.customData.attackPower) {
    this.player.damage = img.frame.customData.attackPower;
}

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

Использование customData во фреймах атласа — это элегантный паттерн для хранения метаданных, специфичных для вашей игры. Он уменьшает жесткую связь между артом и кодом. Для экспериментов попробуйте

  1. Добавить в JSON свойство scale и автоматически масштабировать спрайты при создании
  2. Хранить вероятность выпадения предмета для спрайтов лута
  3. Динамически модифицировать объект atlasJSON в preload, генерируя данные (например, случайные цвета) на лету