О чем этот пример
Создание плавных и органичных путей движения — ключевой навык для оживления игрового мира. В Phaser для этого есть мощный инструмент — кривые-сплайны. В этой статье мы разберем, как из обычного плоского массива координат создать сложную кривую, по которой объект будет двигаться плавно и естественно. Это пригодится для анимации врагов, перемещения камеры по нелинейной траектории или рисования сложных UI-элементов. Мы не только построим кривую, но и анимируем по ней движение точки, используя встроенный tween-движок Phaser. Вы научитесь визуализировать как саму траекторию, так и контрольные точки, которые ее формируют.
Версия Phaser: код и демо в этой статье рассчитаны на Phaser 3.90.0.
Живой запуск
Ниже встроен рабочий билд примера. Оригинальный источник: GitHub.
Исходный код
class Example extends Phaser.Scene
{
graphics;
points;
curve;
path;
create ()
{
this.graphics = this.add.graphics();
this.path = { t: 0, vec: new Phaser.Math.Vector2() };
this.points = [
50, 300,
164, 246,
274, 342,
412, 257,
522, 341,
664, 264
];
this.curve = new Phaser.Curves.Spline(this.points);
this.tweens.add({
targets: this.path,
t: 1,
ease: 'Sine.easeInOut',
duration: 2000,
yoyo: true,
repeat: -1
});
}
update ()
{
this.graphics.clear();
this.graphics.lineStyle(1, 0xffffff, 1);
this.curve.draw(this.graphics, 64);
this.graphics.fillStyle(0x00ff00, 1);
for (let i = 0; i < this.points.length; i++)
{
this.graphics.fillCircle(this.points[i].x, this.points[i].y, 4);
}
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() мы инициализируем все необходимые объекты. Ключевой элемент — массив points. Важно понимать его структуру: это не массив объектов, а плоский список чисел, где каждая пара представляет координаты X и Y одной точки.
this.points = [
50, 300,
164, 246,
274, 342,
412, 257,
522, 341,
664, 264
];
Также мы создаем объект path, который будет хранить текущую позицию на кривой (от 0 до 1) и вектор для вычислений, чтобы не создавать новый вектор каждый кадр.
this.path = { t: 0, vec: new Phaser.Math.Vector2() };
Графический контекст graphics будет использоваться для отрисовки.
Создание и анимация сплайна
Создание кривой происходит в одну строку. Класс Phaser.Curves.Spline принимает наш плоский массив и автоматически интерпретирует его как набор точек.
this.curve = new Phaser.Curves.Spline(this.points);
Чтобы точка двигалась по этой криве, мы используем твин. Целью анимации (targets) является наш объект path, а конкретно — его свойство `t. Твин плавно меняетtот 0 до 1 и обратно (yoyo: true), зацикленно (repeat: -1`).
this.tweens.add({
targets: this.path,
t: 1,
ease: 'Sine.easeInOut',
duration: 2000,
yoyo: true,
repeat: -1
});
Визуализация в реальном времени
Вся отрисовка происходит в update(). Сначала мы очищаем canvas от графики предыдущего кадра.
this.graphics.clear();
Затем задаем стиль линии и рисуем саму кривую. Второй аргумент draw — это количество сегментов для аппроксимации кривой. Чем больше, тем она плавнее.
this.graphics.lineStyle(1, 0xffffff, 1);
this.curve.draw(this.graphics, 64);
Далее мы рисуем зеленые точки, которые задали форму сплайна. Обратите внимание на обращение к координатам: this.points[i].x и this.points[i].y. После создания кривой объект this.points преобразуется из плоского массива в массив объектов Vector2.
this.graphics.fillStyle(0x00ff00, 1);
for (let i = 0; i < this.points.length; i++)
{
this.graphics.fillCircle(this.points[i].x, this.points[i].y, 4);
}
Движущаяся точка по кривой
Самая важная часть — получение текущей позиции на кривой. Метод getPoint принимает нормализованное значение `t(от 0 до 1, где 0 — начало кривой, 1 — конец) и вектор, в который запишется результат. Мы используем заранее созданныйthis.path.vec`.
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);
Что попробовать дальше
Кривые-сплайны в Phaser — это мощный и простой в использовании инструмент для создания сложного движения из минимальных данных. Вы можете экспериментировать: изменять массив точек в реальном времени для динамических траекторий, привязывать к движущейся точке спрайт игрока или врага, или использовать getPoint для расчета позиции множества объектов, создавая, например, строй летящих птиц. Попробуйте заменить Spline на CatmullRom или QuadraticBeier для другого характера сглаживания пути.
