О чем этот пример
Работа с кривыми и путями — основа для создания плавных траекторий движения объектов в играх. Этот пример демонстрирует, как создать простую линейную кривую, визуализировать её, анимировать точку вдоль неё и, что самое важное, сделать концы линии перетаскиваемыми в реальном времени. Такой подход полезен для редакторов уровней, траекторий полёта снарядов или создания гибких путевых точек для NPC.
Версия Phaser: код и демо в этой статье рассчитаны на Phaser 3.90.0.
Живой запуск
Ниже встроен рабочий билд примера. Оригинальный источник: GitHub.
Исходный код
class Example extends Phaser.Scene
{
graphics;
curve;
path;
preload ()
{
this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.spritesheet('dragcircle', 'assets/sprites/dragcircle.png', { frameWidth: 16 });
}
create ()
{
this.graphics = this.add.graphics();
this.path = { t: 0, vec: new Phaser.Math.Vector2() };
this.curve = new Phaser.Curves.Line([ 100, 100, 600, 400 ]);
// curve = new Phaser.Curves.Line(new Phaser.Math.Vector2(100, 100), new Phaser.Math.Vector2(600, 400));
const point0 = this.add.image(this.curve.p0.x, this.curve.p0.y, 'dragcircle', 0).setInteractive();
const point1 = this.add.image(this.curve.p1.x, this.curve.p1.y, 'dragcircle', 0).setInteractive();
point0.setData('vector', this.curve.p0);
point1.setData('vector', this.curve.p1);
this.input.setDraggable([ point0, point1 ]);
this.input.on('dragstart', (pointer, gameObject) =>
{
gameObject.setFrame(1);
});
this.input.on('drag', (pointer, gameObject, dragX, dragY) =>
{
gameObject.x = dragX;
gameObject.y = dragY;
gameObject.data.get('vector').set(dragX, dragY);
});
this.input.on('dragend', (pointer, gameObject) =>
{
gameObject.setFrame(0);
});
this.tweens.add({
targets: this.path,
t: 1,
ease: 'Sine.easeInOut',
duration: 2000,
yoyo: true,
repeat: -1
});
}
update ()
{
this.graphics.clear();
this.graphics.lineStyle(2, 0xffffff, 1);
this.curve.draw(this.graphics);
this.curve.getPoint(this.path.t, this.path.vec);
this.graphics.fillStyle(0xff0000, 1);
this.graphics.fillRect(this.path.vec.x - 8, this.path.vec.y - 8, 16, 16);
}
}
const config = {
type: Phaser.AUTO,
width: 800,
height: 600,
backgroundColor: '#2d2d2d',
parent: 'phaser-example',
scene: Example
};
const game = new Phaser.Game(config);
Подготовка сцены и создание кривой
В методе preload загружается спрайт для визуальных маркеров. Основная работа начинается в create.
Сначала создаётся объект Graphics для отрисовки. Затем определяется объект path, который будет хранить текущую позицию (параметр `t` от 0 до 1) на кривой и вектор для вычислений.
Кривая создаётся с помощью класса Phaser.Curves.Line. Её можно инициализировать массивом координат или двумя объектами Vector2.
this.curve = new Phaser.Curves.Line([100, 100, 600, 400]);
// Альтернатива:
// curve = new Phaser.Curves.Line(new Phaser.Math.Vector2(100, 100), new Phaser.Math.Vector2(600, 400));
Точки p0 и p1 кривой — это векторы, определяющие начало и конец линии.
Интерактивные точки и перетаскивание
Чтобы сделать кривую интерактивной, в позициях её концов размещаются спрайты. Каждому спрайту через setData привязывается ссылка на соответствующий вектор кривой (p0 или p1). Это ключевой момент для связи графического объекта с математическими данными.
point0.setData('vector', this.curve.p0);
point1.setData('vector', this.curve.p1);
Затем спрайты делаются перетаскиваемыми с помощью this.input.setDraggable. Обработчики событий dragstart, drag и dragend меняют кадр спрайта (визуальный feedback) и, что самое важное, обновляют координаты привязанного вектора кривой в реальном времени во время перетаскивания.
this.input.on('drag', (pointer, gameObject, dragX, dragY) => {
gameObject.x = dragX;
gameObject.y = dragY;
gameObject.data.get('vector').set(dragX, dragY);
});
Таким образом, при перемещении маркера сама геометрическая кривая немедленно изменяется.
Анимация движения по кривой
Для создания циклического движения точки вдоль линии используется твин. Он анимирует свойство `tобъектаpathот 0 до 1 и обратно (yoyo: true`).
this.tweens.add({
targets: this.path,
t: 1,
ease: 'Sine.easeInOut',
duration: 2000,
yoyo: true,
repeat: -1
});
Параметр `t(от 0 в начале линии до 1 в её конце) определяет позицию на кривой. В методеupdate` это значение используется для вычисления текущих координат точки.
Визуализация в реальном времени
Каждый кадр в update происходит перерисовка. Сначала очищается холст Graphics, задаётся стиль линии и рисуется сама кривая методом this.curve.draw(this.graphics).
Затем, используя текущее значение this.path.t, вычисляется позиция точки на кривой. Метод getPoint записывает координаты в переданный вектор this.path.vec.
this.curve.getPoint(this.path.t, this.path.vec);
На этих координатах рисуется красный квадрат, который и представляет движущуюся точку. Поскольку перерисовка происходит каждые 16ms (при ~60fps), а кривая обновляется сразу при драге, мы получаем полностью интерактивную и анимированную сцену.
Что попробовать дальше
Этот пример — отличная основа для множества игровых механик. Вы можете экспериментировать: заменить Line на Spline или Quadratic Bezier для более сложных траекторий, привязать к движущейся точке спрайт игрока или врага, использовать массив таких кривых для построения сложного пути или сохранять конечные позиции точек в JSON для последующей загрузки уровня. Интерактивность кривых открывает двери к созданию внутриигровых редакторов.
