О чем этот пример
Создание сложных и плавных траекторий движения — ключ к визуально интересной игровой механике. Встроенные кривые Phaser, такие как `Ellipse`, избавляют от необходимости ручного расчета координат по тригонометрическим формулам. В этой статье мы разберем, как создать эллиптическую траекторию, заставить объект двигаться по ней и, что более интересно, как динамически менять форму самой кривой, создавая эффект спирали или пульсирующего орбитального пути. Этот подход идеально подходит для полетов вражеских кораблей, орбитального движения планет или магических заклинаний.
Версия Phaser: код и демо в этой статье рассчитаны на Phaser 3.90.0.
Живой запуск
Ниже встроен рабочий билд примера. Оригинальный источник: GitHub.
Исходный код
class Example extends Phaser.Scene
{
graphics;
curve;
path;
create ()
{
this.graphics = this.add.graphics();
this.path = { t: 0, vec: new Phaser.Math.Vector2() };
// x, y, xRadius, yRadius, startAngle, endAngle, clockwise, rotation
// curve = new Phaser.Curves.Ellipse(400, 300, 100, 260, 0, 180, false);
// With minimal arguments it creates a circle of radius 260 centered on 400x300:
this.curve = new Phaser.Curves.Ellipse(400, 300, 260);
this.tweens.add({
targets: this.path,
t: 1,
ease: 'Linear',
duration: 4000,
repeat: -1
});
// By adjusting the radius you can create a spiral effect
this.tweens.add({
targets: this.curve,
xRadius: 50,
yRadius: 200,
ease: 'Sine.easeInOut',
duration: 16000,
yoyo: true,
repeat: -1
});
}
update ()
{
this.graphics.clear();
this.graphics.lineStyle(2, 0xffffff, 1);
this.curve.draw(this.graphics, 64);
this.curve.getPoint(this.path.t, this.path.vec);
this.graphics.fillStyle(0xff0000, 1);
this.graphics.fillCircle(this.path.vec.x, this.path.vec.y, 8);
}
}
const config = {
type: Phaser.AUTO,
width: 800,
height: 600,
backgroundColor: '#2d2d2d',
parent: 'phaser-example',
scene: Example
};
const game = new Phaser.Game(config);
Инициализация сцены и создание кривой
В методе create() мы подготавливаем все необходимые объекты. Сначала создаем контейнер graphics для отрисовки.
Затем определяем объект path, который будет хранить текущую позицию на кривой (от 0 до 1) и вектор для вычисленных координат. Это наш "курсор" вдоль пути.
Самой важной частью является создание кривой. Конструктор Phaser.Curves.Ellipse может принимать разное количество аргументов для тонкой настройки, но в самом простом виде он создает круг.
this.path = { t: 0, vec: new Phaser.Math.Vector2() };
this.curve = new Phaser.Curves.Ellipse(400, 300, 260);
Первые два аргумента (400, 300) — это координаты центра эллипса на холсте. Третий аргумент (260) задает радиус. Если передать только один радиус, как в нашем случае, будет создана идеальная окружность с центром в точке (400, 300) и радиусом 260 пикселей.
Анимация движения точки по кривой
Чтобы точка двигалась по созданной траектории, мы используем твин из системы this.tweens. Мы анимируем свойство `tнашего объектаpath` от 0 до 1.
this.tweens.add({
targets: this.path,
t: 1,
ease: 'Linear',
duration: 4000,
repeat: -1
});
Здесь targets — это объект, свойство которого мы будем изменять. Свойство `tпредставляет собой нормализованную позицию на кривой (0 — начало, 1 — конец).ease: 'Linear'гарантирует, что движение будет равномерным по всей длине кривой, без ускорений и замедлений. Параметрrepeat: -1` заставляет анимацию повторяться бесконечно.
В методе update() мы вычисляем текущие координаты точки на основе значения `t` и отрисовываем ее.
this.curve.getPoint(this.path.t, this.path.vec);
this.graphics.fillCircle(this.path.vec.x, this.path.vec.y, 8);
Метод curve.getPoint(t, vector) записывает в переданный вектор this.path.vec координаты точки на кривой для заданного значения `t`.
Динамическое изменение формы кривой (эффект спирали)
Самая мощная часть примера — это возможность анимировать не только движение *по* кривой, но и саму *форму* кривой. Это создает сложные составные траектории, например, спираль.
Мы создаем второй твин, который воздействует уже на саму кривую this.curve, изменяя ее радиусы.
this.tweens.add({
targets: this.curve,
xRadius: 50,
yRadius: 200,
ease: 'Sine.easeInOut',
duration: 16000,
yoyo: true,
repeat: -1
});
Здесь targets — это объект кривой. Мы анимируем два ее свойства: xRadius (радиус по оси X) и yRadius (радиус по оси Y). Твин плавно изменит кривую от начального состояния (круг с радиусом 260) до эллипса с радиусами 50 и 200, а затем, благодаря yoyo: true, вернет ее обратно. Плавность перехода обеспечивает ease: 'Sine.easeInOut'.
Поскольку точка продолжает двигаться по этой изменяющейся кривой, ее результирующая траектория становится сложной спиралевидной фигурой, а не простым эллипсом.
Отрисовка и обновление в реальном времени
Каждый кадр в методе update() мы перерисовываем сцену, чтобы отразить все изменения: новую форму кривой и новое положение точки.
update ()
{
// Очищаем графику от предыдущего кадра
this.graphics.clear();
// Задаем стиль линии для отрисовки самой кривой
this.graphics.lineStyle(2, 0xffffff, 1);
// Рисуем саму кривую, разбивая ее на 64 сегмента для гладкости
this.curve.draw(this.graphics, 64);
// ... (расчет и отрисовка точки)
}
Ключевой метод curve.draw(graphics, segments) рисует саму кривую на холсте graphics. Второй аргумент (64) определяет количество сегментов, на которые разбивается кривая для аппроксимации. Чем больше сегментов, тем более гладкой будет выглядеть кривая на экране, особенно при сильных искажениях формы.
Важно: вызов this.graphics.clear() в начале метода update() стирает все, что было нарисовано в предыдущем кадре. Без этого мы бы видели на экране "шлейф" из всех предыдущих положений кривой и точки.
Что попробовать дальше
Класс Phaser.Curves.Ellipse — это мощный инструмент для декларативного описания сложных траекторий. Комбинируя твины для движения точки по кривой и для изменения параметров самой кривой, можно создавать динамические, живые пути практически любой сложности без глубокого погружения в математику. Для экспериментов попробуйте: заменить Ellipse на Curves.Path и сконструировать путь из нескольких сегментов; привязать к движущейся точке не красный круг, а спрайт вражеского корабля; или использовать значение `t` для синхронизации с ним других процессов, например, скорости стрельбы.
