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

Создание анимаций в коде может стать громоздким, особенно когда их много. Phaser предоставляет мощный метод `anims.fromJSON()`, позволяющий загружать и создавать наборы анимаций из декларативных JSON-объектов. Это полезно для отделения данных анимации от логики игры, что упрощает редактирование, переиспользование и загрузку анимаций из внешних файлов или с сервера. В этой статье мы разберем пример, где четыре анимированных спрайта оживают благодаря единственному вызову с JSON-конфигурацией.

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

Живой запуск

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

Исходный код


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

    preload ()
    {
        this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
        this.load.atlas('gems', 'assets/tests/columns/gems.png', 'assets/tests/columns/gems.json');

    }

    create ()
    {
        this.add.text(400, 32, 'Animations from JSON Object', { color: '#00ff00' }).setOrigin(0.5, 0);

        this.anims.fromJSON(data);

        this.add.sprite(400, 200, 'gems').play('diamond');
        this.add.sprite(400, 300, 'gems').play('prism');
        this.add.sprite(400, 400, 'gems').play('ruby');
        this.add.sprite(400, 500, 'gems').play('square');
    }
}

const config = {
    type: Phaser.AUTO,
    parent: 'phaser-example',
    width: 800,
    height: 600,
    scene: Example
};

const game = new Phaser.Game(config);

// Hoisting
const data = {
    "anims": [
        {
            "key": "diamond",
            "type": "frame",
            "frames": [
                {
                    "key": "gems",
                    "frame": "diamond_0000",
                    "duration": 0,
                    "visible": false
                },
                {
                    "key": "gems",
                    "frame": "diamond_0001",
                    "duration": 0,
                    "visible": false
                },
                {
                    "key": "gems",
                    "frame": "diamond_0002",
                    "duration": 0,
                    "visible": false
                },
                {
                    "key": "gems",
                    "frame": "diamond_0003",
                    "duration": 0,
                    "visible": false
                },
                {
                    "key": "gems",
                    "frame": "diamond_0004",
                    "duration": 0,
                    "visible": false
                },
                {
                    "key": "gems",
                    "frame": "diamond_0005",
                    "duration": 0,
                    "visible": false
                },
                {
                    "key": "gems",
                    "frame": "diamond_0006",
                    "duration": 0,
                    "visible": false
                },
                {
                    "key": "gems",
                    "frame": "diamond_0007",
                    "duration": 0,
                    "visible": false
                },
                {
                    "key": "gems",
                    "frame": "diamond_0008",
                    "duration": 0,
                    "visible": false
                },
                {
                    "key": "gems",
                    "frame": "diamond_0009",
                    "duration": 0,
                    "visible": false
                },
                {
                    "key": "gems",
                    "frame": "diamond_0010",
                    "duration": 0,
                    "visible": false
                },
                {
                    "key": "gems",
                    "frame": "diamond_0011",
                    "duration": 0,
                    "visible": false
                },
                {
                    "key": "gems",
                    "frame": "diamond_0012",
                    "duration": 0,
                    "visible": false
                },
                {
                    "key": "gems",
                    "frame": "diamond_0013",
                    "duration": 0,
                    "visible": false
                },
                {
                    "key": "gems",
                    "frame": "diamond_0014",
                    "duration": 0,
                    "visible": false
                },
                {
                    "key": "gems",
                    "frame": "diamond_0015",
                    "duration": 0,
                    "visible": false
                }
            ],
            "frameRate": 24,
            "duration": 1.5,
            "skipMissedFrames": true,
            "delay": 0,
            "repeat": -1,
            "repeatDelay": 0,
            "yoyo": false,
            "showOnStart": false,
            "hideOnComplete": false
        },
        {
            "key": "prism",
            "type": "frame",
            "frames": [
                {
                    "key": "gems",
                    "frame": "prism_0000",
                    "duration": 0,
                    "visible": false
                },
                {
                    "key": "gems",
                    "frame": "prism_0001",
                    "duration": 0,
                    "visible": false
                },
                {
                    "key": "gems",
                    "frame": "prism_0002",
                    "duration": 0,
                    "visible": false
                },
                {
                    "key": "gems",
                    "frame": "prism_0003",
                    "duration": 0,
                    "visible": false
                },
                {
                    "key": "gems",
                    "frame": "prism_0004",
                    "duration": 0,
                    "visible": false
                },
                {
                    "key": "gems",
                    "frame": "prism_0005",
                    "duration": 0,
                    "visible": false
                },
                {
                    "key": "gems",
                    "frame": "prism_0006",
                    "duration": 0,
                    "visible": false
                }
            ],
            "frameRate": 24,
            "duration": 3.4285714285714284,
            "skipMissedFrames": true,
            "delay": 0,
            "repeat": -1,
            "repeatDelay": 0,
            "yoyo": false,
            "showOnStart": false,
            "hideOnComplete": false
        },
        {
            "key": "ruby",
            "type": "frame",
            "frames": [
                {
                    "key": "gems",
                    "frame": "ruby_0000",
                    "duration": 0,
                    "visible": false
                },
                {
                    "key": "gems",
                    "frame": "ruby_0001",
                    "duration": 0,
                    "visible": false
                },
                {
                    "key": "gems",
                    "frame": "ruby_0002",
                    "duration": 0,
                    "visible": false
                },
                {
                    "key": "gems",
                    "frame": "ruby_0003",
                    "duration": 0,
                    "visible": false
                },
                {
                    "key": "gems",
                    "frame": "ruby_0004",
                    "duration": 0,
                    "visible": false
                },
                {
                    "key": "gems",
                    "frame": "ruby_0005",
                    "duration": 0,
                    "visible": false
                },
                {
                    "key": "gems",
                    "frame": "ruby_0006",
                    "duration": 0,
                    "visible": false
                }
            ],
            "frameRate": 24,
            "duration": 3.4285714285714284,
            "skipMissedFrames": true,
            "delay": 0,
            "repeat": -1,
            "repeatDelay": 0,
            "yoyo": false,
            "showOnStart": false,
            "hideOnComplete": false
        },
        {
            "key": "square",
            "type": "frame",
            "frames": [
                {
                    "key": "gems",
                    "frame": "square_0000",
                    "duration": 0,
                    "visible": false
                },
                {
                    "key": "gems",
                    "frame": "square_0001",
                    "duration": 0,
                    "visible": false
                },
                {
                    "key": "gems",
                    "frame": "square_0002",
                    "duration": 0,
                    "visible": false
                },
                {
                    "key": "gems",
                    "frame": "square_0003",
                    "duration": 0,
                    "visible": false
                },
                {
                    "key": "gems",
                    "frame": "square_0004",
                    "duration": 0,
                    "visible": false
                },
                {
                    "key": "gems",
                    "frame": "square_0005",
                    "duration": 0,
                    "visible": false
                },
                {
                    "key": "gems",
                    "frame": "square_0006",
                    "duration": 0,
                    "visible": false
                },
                {
                    "key": "gems",
                    "frame": "square_0007",
                    "duration": 0,
                    "visible": false
                },
                {
                    "key": "gems",
                    "frame": "square_0008",
                    "duration": 0,
                    "visible": false
                },
                {
                    "key": "gems",
                    "frame": "square_0009",
                    "duration": 0,
                    "visible": false
                },
                {
                    "key": "gems",
                    "frame": "square_0010",
                    "duration": 0,
                    "visible": false
                },
                {
                    "key": "gems",
                    "frame": "square_0011",
                    "duration": 0,
                    "visible": false
                },
                {
                    "key": "gems",
                    "frame": "square_0012",
                    "duration": 0,
                    "visible": false
                },
                {
                    "key": "gems",
                    "frame": "square_0013",
                    "duration": 0,
                    "visible": false
                },
                {
                    "key": "gems",
                    "frame": "square_0014",
                    "duration": 0,
                    "visible": false
                }
            ],
            "frameRate": 24,
            "duration": 1.6,
            "skipMissedFrames": true,
            "delay": 0,
            "repeat": -1,
            "repeatDelay": 0,
            "yoyo": false,
            "showOnStart": false,
            "hideOnComplete": false
        }
    ],
    "globalTimeScale": 1
};

Структура примера и загрузка атласа

В примере используется одна сцена (Scene), которая в методе preload загружает текстуру атласа. Атлас — это изображение (gems.png), содержащее множество кадров (спрайтов), и JSON-файл (gems.json) с координатами каждого кадра.

this.load.atlas('gems', 'assets/tests/columns/gems.png', 'assets/tests/columns/gems.json');

Ключ 'gems' используется для идентификации этого атласа в кеше. Важно понимать, что на этом этапе загружаются только статические изображения. Сами анимации как последовательности кадров еще не созданы.

Создание анимаций из JSON-объекта

Вся магия происходит в методе create. С помощью вызова this.anims.fromJSON(data) система анимаций Phaser парсит предоставленный объект и регистрирует все описанные в нем анимации.

this.anims.fromJSON(data);

Объект data, определенный в конце файла, содержит массив anims. Каждый элемент этого массива — конфигурация для одной анимации. После выполнения этой строки в глобальном менеджере анимаций (this.anims) становятся доступны анимации с ключами 'diamond', 'prism', 'ruby' и 'square'. Они готовы к использованию любыми спрайтами, которые используют тот же текстурый атлас 'gems'.

Анатомия JSON-конфигурации анимации

Давайте детально разберем структуру объекта для одной анимации на примере 'diamond'. Это позволит вам создавать свои конфигурации.

{
    "key": "diamond",
    "type": "frame",
    "frames": [
        { "key": "gems", "frame": "diamond_0000", "duration": 0 },
        // ... остальные кадры
    ],
    "frameRate": 24,
    "repeat": -1,
    "yoyo": false
}

* key: Уникальный идентификатор анимации для её последующего вызова через .play(). * type: "frame" указывает, что анимация состоит из отдельных кадров. * frames: Массив объектов, каждый из которых ссылается на конкретный кадр в атласе 'gems' по имени (frame). Поле duration для каждого кадра установлено в 0, что означает использование глобального frameRate. * frameRate: Количество кадров в секунду. Рассчитанная duration анимации (1.5 сек) выводится из количества кадров и этого значения. * repeat: -1 означает бесконечное повторение анимации. * yoyo: false — анимация не будет проигрываться в обратном порядке при повторе. Остальные параметры, такие как delay, repeatDelay, showOnStart, управляют дополнительным поведением.

Применение анимации к спрайтам

После регистрации анимаций их можно присвоить спрайтам. В примере создается четыре спрайта с разными координатами по Y, но все используют одну текстуру 'gems'.

this.add.sprite(400, 200, 'gems').play('diamond');
this.add.sprite(400, 300, 'gems').play('prism');
// ... и так далее

Метод add.sprite() создает и добавляет спрайт на сцену. Цепочный вызов .play('diamond') немедленно запускает на этом спрайте анимацию с указанным ключом. Phaser автоматически связывает кадры из атласа, описанные в JSON, с этим спрайтом, и анимация начинает проигрываться циклически (благодаря "repeat": -1).

Практические преимущества и сценарии использования

Использование fromJSON() — это не просто синтаксический сахар. Это архитектурный подход.

* **Разделение данных и кода:** Конфигурацию анимаций можно хранить в отдельных .json файлах и загружать через this.load.json(). Это делает код игры чище, а данные — легче для редактирования художниками или геймдизайнерами. * **Динамическая загрузка:** Вы можете генерировать JSON-конфигурации на стороне сервера или загружать разные наборы анимаций для разных уровней. * **Экспорт из редакторов:** Многие инструменты для создания спрайтов и анимаций (например, TexturePacker, Adobe Animate) могут экспортировать данные в формате, совместимом с Phaser JSON. Метод fromJSON() становится мостом между редактором контента и игровым движком.

// Пример: загрузка анимаций из внешнего файла
preload() {
    this.load.atlas('characters', 'assets/char.png', 'assets/char.json');
    this.load.json('animationsData', 'assets/animations.json');
}
create() {
    const animData = this.cache.json.get('animationsData');
    this.anims.fromJSON(animData);
}

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

Метод anims.fromJSON() в Phaser — это мощный инструмент для декларативного управления анимациями. Он позволяет выносить их конфигурацию из исполняемого кода, что повышает гибкость и поддерживаемость проекта. Для экспериментов попробуйте: 1. Изменить параметры repeatDelay или yoyo в JSON, чтобы создать новые паттерны повторения анимации. 2. Вынести объект data в отдельный файл animations.json и загружать его через this.load.json. 3. Создать несколько спрайтов, которые используют одну и ту же зарегистрированную анимацию, и управлять их воспроизведением независимо (ставить на паузу, менять скорость через timeScale). 4. Скомбинировать этот подход с анимацией по спрайт-листу (spritesheet), указав в конфигурации "type": "spriteframe".