О чем этот пример
Создание сложных траекторий движения — ключевой элемент для оживления игровых миров. В этой статье мы разберем, как с помощью встроенного API кривых Phaser 3 построить зигзагообразный путь и заставить спрайты плавно следовать по нему. Этот подход идеально подходит для создания конвейеров, патрульных маршрутов врагов или декоративных анимаций частиц без необходимости ручного расчета координат.
Версия Phaser: код и демо в этой статье рассчитаны на Phaser 3.90.0.
Живой запуск
Ниже встроен рабочий билд примера. Оригинальный источник: GitHub.
Исходный код
class Example extends Phaser.Scene
{
followers;
graphics;
path;
preload ()
{
this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.image('ball', 'assets/sprites/shinyball.png');
}
create ()
{
this.graphics = this.add.graphics();
// Create the zig-zag path
this.path = new Phaser.Curves.Path(100, -50);
this.path.lineTo(100, 50);
const max = 8;
const h = 500 / max;
for (let i = 0; i < max; i++)
{
if (i % 2 === 0)
{
this.path.lineTo(700, 50 + h * (i + 1));
}
else
{
this.path.lineTo(100, 50 + h * (i + 1));
}
}
this.path.lineTo(100, 650);
// Create the path followers
this.followers = this.add.group();
for (let i = 0; i < 32; i++)
{
const ball = this.followers.create(0, -50, 'ball');
ball.setData('vector', new Phaser.Math.Vector2());
this.tweens.add({
targets: ball,
z: 1,
ease: 'Linear',
duration: 12000,
repeat: -1,
delay: i * 100
});
}
}
update ()
{
this.graphics.clear();
this.graphics.lineStyle(1, 0xffffff, 1);
this.path.draw(this.graphics);
const balls = this.followers.getChildren();
for (let i = 0; i < balls.length; i++)
{
const t = balls[i].z;
const vec = balls[i].getData('vector');
// The vector is updated in-place
this.path.getPoint(t, vec);
balls[i].setPosition(vec.x, vec.y);
balls[i].setDepth(balls[i].y);
}
}
}
const config = {
type: Phaser.AUTO,
width: 800,
height: 600,
backgroundColor: '#2d2d2d',
parent: 'phaser-example',
scene: Example
};
const game = new Phaser.Game(config);
Создание и визуализация пути
Основу анимации составляет объект Phaser.Curves.Path. Он позволяет последовательно строить путь из сегментов — линий, сплайнов или дуг.
В методе create() мы создаем путь, начиная с точки (100, -50). Затем добавляем первый сегмент до точки (100, 50). Далее в цикле формируется зигзаг: если индекс четный, линия проводится вправо к X=700, если нечетный — обратно влево к X=100. Координата Y с каждой итерацией увеличивается, создавая ступенчатую структуру.
Для отладки и визуализации путь рисуется с помощью объекта Graphics. В update() мы каждый кадр очищаем старую отрисовку и заново рисуем линию пути белым цветом.
this.path = new Phaser.Curves.Path(100, -50);
this.path.lineTo(100, 50);
const max = 8;
const h = 500 / max;
for (let i = 0; i < max; i++)
{
if (i % 2 === 0)
{
this.path.lineTo(700, 50 + h * (i + 1));
}
else
{
this.path.lineTo(100, 50 + h * (i + 1));
}
}
this.path.lineTo(100, 650);
// В методе update():
this.graphics.clear();
this.graphics.lineStyle(1, 0xffffff, 1);
this.path.draw(this.graphics);
Подготовка последователей (followers)
Для движения по пути создается группа спрайтов (Group). Каждому спрайту-«шарику» в пользовательские данные (setData) записывается вектор — экземпляр Phaser.Math.Vector2. Этот вектор будет использоваться как буфер для получения координат точки на кривой, что эффективнее, чем создание нового объекта каждый кадр.
Ключевой момент — анимация свойства `zс помощьюtweens. Хотяzобычно ассоциируется с глубиной, здесь оно творчески переиспользуется как прогресс движения вдоль пути (значениеtот 0 до 1). Каждый следующий шар получает задержку (delay`), что создает волнообразный эффект.
this.followers = this.add.group();
for (let i = 0; i < 32; i++)
{
const ball = this.followers.create(0, -50, 'ball');
ball.setData('vector', new Phaser.Math.Vector2());
this.tweens.add({
targets: ball,
z: 1,
ease: 'Linear',
duration: 12000,
repeat: -1,
delay: i * 100
});
}
Обновление позиций и управление глубиной
В каждом кадре игры, в методе update(), для каждого шара из группы происходит обновление его позиции на экране.
Сначала из свойства `zшара, которое анимируется твином, получаем значение прогрессаt. Затем, используя метод путиgetPoint(t, vector)`, мы вычисляем координаты точки на пути для данного прогресса. Второй аргумент — вектор — передается для обновления «на месте» (in-place), что избегает создания мусора.
Полученные координаты (vec.x, vec.y) задаются как позиция шара через setPosition. Дополнительно, глубина отрисовки (depth) шара привязывается к его координате Y, чтобы шары, находящиеся ниже по экрану, перекрывали те, что выше, создавая простую параллакс-перспективу.
const balls = this.followers.getChildren();
for (let i = 0; i < balls.length; i++)
{
const t = balls[i].z;
const vec = balls[i].getData('vector');
// Вектор обновляется in-place
this.path.getPoint(t, vec);
balls[i].setPosition(vec.x, vec.y);
balls[i].setDepth(balls[i].y);
}
Что попробовать дальше
Комбинируя Curves.Path, Tweens и Groups, можно создавать сложные и производительные паттерны движения. Для экспериментов попробуйте: заменить lineTo на splineTo для плавных изгибов, изменить расчет глубины для другого 3D-эффекта или запустить твины с разной длительностью для разнообразия движения.
