О чем этот пример
Создание анимаций в коде может стать громоздким, особенно когда их много. 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".
