О чем этот пример
Часто игровые анимации приходят извне в формате GIF или видео, а разработчику нужно превратить их в эффективный игровой ассет. В этом примере показан практический подход к загрузке и управлению анимацией, кадры которой распределены между несколькими текстурными атласами. Вы научитесь динамически собирать последовательность кадров, создавать групповые анимации и применять плавные трансформации к множеству объектов одновременно — навык, критичный для создания визуально насыщенных эффектов в играх.
Версия Phaser: код и демо в этой статье рассчитаны на Phaser 3.90.0.
Живой запуск
Ниже встроен рабочий билд примера. Оригинальный источник: GitHub.
Исходный код
class Example extends Phaser.Scene
{
constructor ()
{
super();
this.data = {};
}
init() {
this.data = {
r: -0.05,
s: -0.0012,
sx: 0.25,
x: 400,
y: 100
};
}
preload ()
{
this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.setPath('assets/animations/');
this.load.atlas('sao0');
this.load.atlas('sao1');
}
create ()
{
// Our animation consists of 50 frames split across 2 texture atlases:
// sao0 contains frames: 0, 1, 4, 7, 8, 9, 10, 11, 16, 17, 18, 19, 23, 24, 25, 26, 30, 31, 32, 33, 38, 39, 40, 45, 46, 47, 48
// sao1 contains frames: 2, 3, 5, 6, 12, 13, 14, 20, 21, 22, 27, 28, 29, 34, 35, 36, 37, 41, 42, 43, 44, 49, 50
// Let's create an array to hold them all:
var frames = [];
const sao0 = [ 0, 1, 4, 7, 8, 9, 10, 11, 16, 17, 18, 19, 23, 24, 25, 26, 30, 31, 32, 33, 38, 39, 40, 45, 46, 47, 48 ];
const sao1 = [ 2, 3, 5, 6, 12, 13, 14, 20, 21, 22, 27, 28, 29, 34, 35, 36, 37, 41, 42, 43, 44, 49, 50 ];
// And insert the frames into the array:
for (var i = 0; i <= 50; i++)
{
if (sao0.indexOf(i) > -1)
{
frames.push({ key: 'sao0', frame: i.toString() });
}
else
{
frames.push({ key: 'sao1', frame: i.toString() });
}
}
// All the 'frames' array needs are objects that contain the key of the texture and the 'frame'
// property, which is the name of our frame within the atlas (in this case they're just numbers)
this.anims.create({
key: 'swish',
frames: frames,
repeat: -1
});
this.group = this.add.group();
this.group.createMultiple({ key: 'sao', repeat: 10, setXY: { x: 400, y: 300 }, setAlpha: { value: 0, step: 0.05 } });
this.group.playAnimation('swish');
this.tweens.add({
targets: this.data,
duration: 3000,
ease: 'Sine.easeInOut',
yoyo: true,
repeat: -1,
props: {
r: {
value: 0.05
},
s: {
value: 0.0012
},
sx: {
value: 2.5
},
y: {
value: 400,
duration: 4000
}
}
});
}
update ()
{
const children = this.group.getChildren();
Phaser.Actions.Rotate(children, this.data.r, this.data.s);
Phaser.Actions.SetScale(children, this.data.sx, this.data.sx, this.data.s, this.data.s);
Phaser.Actions.SetXY(children, this.data.x, this.data.y, this.data.s, this.data.s);
}
}
const config = {
type: Phaser.AUTO,
parent: 'phaser-example',
width: 800,
height: 600,
pixelArt: true,
scene: Example
};
const game = new Phaser.Game(config);
Подготовка данных и загрузка атласов
Класс сцены инициализирует объект this.data для хранения параметров трансформаций, которые будут анимированы позже. В методе preload загружаются два текстурных атласа (sao0 и sao1), содержащие разделённые кадры одной анимации. Важно использовать setBaseURL и setPath для корректного указания пути к удалённым или локальным ресурсам.
this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.setPath('assets/animations/');
this.load.atlas('sao0');
this.load.atlas('sao1');
Сборка анимации из нескольких атласов
Кадры анимации распределены между двумя атласами не по порядку. В коде определены два массива, sao0 и sao1, которые содержат номера кадров, хранящихся в каждом из атласов. Цикл от 0 до 50 проверяет принадлежность кадра к одному из массивов и формирует общий массив frames, где каждый элемент — объект с ключом текстуры (key) и именем кадра (frame). Это обязательный формат для создания анимации в Phaser.
const sao0 = [ 0, 1, 4, 7, 8, 9, 10, 11, 16, 17, 18, 19, 23, 24, 25, 26, 30, 31, 32, 33, 38, 39, 40, 45, 46, 47, 48 ];
const sao1 = [ 2, 3, 5, 6, 12, 13, 14, 20, 21, 22, 27, 28, 29, 34, 35, 36, 37, 41, 42, 43, 44, 49, 50 ];
var frames = [];
for (var i = 0; i <= 50; i++) {
if (sao0.indexOf(i) > -1) {
frames.push({ key: 'sao0', frame: i.toString() });
} else {
frames.push({ key: 'sao1', frame: i.toString() });
}
}
Затем анимация создаётся с помощью this.anims.create. Ключ 'swish' будет использоваться для её воспроизведения, а repeat: -1 задаёт бесконечное повторение.
this.anims.create({
key: 'swish',
frames: frames,
repeat: -1
});
Групповое создание и анимация спрайтов
Для создания множества одинаковых спрайтов используется группа (this.add.group). Метод createMultiple генерирует 11 спрайтов (10 повторений + 1 исходный) с текстовым ключом 'sao'. Параметр setXY размещает их в одной точке (400, 300), а setAlpha задаёт постепенное увеличение прозрачности для каждого следующего спрайта, создавая эффект шлейфа. Метод playAnimation запускает анимацию 'swish' на всех спрайтах группы одновременно.
this.group = this.add.group();
this.group.createMultiple({
key: 'sao',
repeat: 10,
setXY: { x: 400, y: 300 },
setAlpha: { value: 0, step: 0.05 }
});
this.group.playAnimation('swish');
Анимация параметров через твины
Параметры в объекте this.data (вращение, масштаб, позиция) анимируются с помощью системы твинов Phaser. Твин длительностью 3000 мс плавно меняет значения свойств, а флаги yoyo: true и repeat: -1 создают циклическую анимацию «туда-обратно». Для свойства `y` задана отдельная длительность (4000 мс), что демонстрирует возможность тонкой настройки.
this.tweens.add({
targets: this.data,
duration: 3000,
ease: 'Sine.easeInOut',
yoyo: true,
repeat: -1,
props: {
r: { value: 0.05 },
s: { value: 0.0012 },
sx: { value: 2.5 },
y: { value: 400, duration: 4000 }
}
});
Применение трансформаций в реальном времени
В методе update текущие значения из this.data применяются ко всем спрайтам группы каждому кадру. Phaser.Actions.Rotate добавляет вращение, Phaser.Actions.SetScale меняет масштаб, а Phaser.Actions.SetXY обновляет позицию. Параметры this.data.s используются как шаг инкремента для масштаба и позиции, создавая волнообразное расхождение спрайтов.
const children = this.group.getChildren();
Phaser.Actions.Rotate(children, this.data.r, this.data.s);
Phaser.Actions.SetScale(children, this.data.sx, this.data.sx, this.data.s, this.data.s);
Phaser.Actions.SetXY(children, this.data.x, this.data.y, this.data.s, this.data.s);
Что попробовать дальше
Этот пример показывает мощь Phaser в работе со сложными анимациями: от сборки кадров из нескольких источников до синхронного управления десятками объектов. Для экспериментов попробуйте изменить алгоритм распределения кадров по атласам, добавить цветовые эффекты через Phaser.Actions.SetTint или заменить твины на физическое движение для более «живого» поведения.
