О чем этот пример
В играх часто нужно, чтобы объекты плавно двигались по заданному пути — например, вражеский корабль по траектории или камера в сцене. Ручной расчёт таких путей сложен и негибок. Phaser предоставляет мощный инструмент — класс `Phaser.Curves`. В этой статье на конкретном примере разберём, как создать гладкую кривую по точкам и как в реальном времени перемещать по ней объект, используя координаты указателя мыши. Этот подход универсален и может быть использован для патрулирования, анимации движения или создания динамических траекторий.
Версия Phaser: код и демо в этой статье рассчитаны на Phaser 3.90.0.
Живой запуск
Ниже встроен рабочий билд примера. Оригинальный источник: GitHub.
Исходный код
class Example extends Phaser.Scene
{
constructor ()
{
super();
}
create ()
{
const graphics = this.add.graphics().lineStyle(1, 0xffffff, 1);
const points = [
50, 300,
164, 246,
274, 302,
412, 400,
522, 241,
664, 364,
750, 400
];
const startX = points[0];
const endX = points[points.length - 2];
const curve = new Phaser.Curves.Spline(points);
curve.draw(graphics, 64);
const point = curve.getPointAt(0);
const location = this.add.rectangle(point.x, point.y, 16, 16, 0xff00ff, 0.8);
this.input.on('pointermove', pointer => {
// getPointAt requires a value between 0 and 1 (start and end of curve)
// We know the start and end x coordinate of the curve, so we can calculate it from that
let px = pointer.worldX;
const distance = endX - startX;
if (px >= startX && px <= endX)
{
px -= startX;
curve.getPointAt(px / distance, point);
location.setPosition(point.x, point.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.Spline позволяет построить гладкую кривую, проходящую через заданные точки. Это удобно для создания органичных, нелинейных путей.
Мы создаём массив points, где координаты идут последовательно: x1, y1, x2, y2 и так далее. Затем этот массив передаётся в конструктор сплайна.
const points = [
50, 300,
164, 246,
274, 302,
412, 400,
522, 241,
664, 364,
750, 400
];
const curve = new Phaser.Curves.Spline(points);
Чтобы визуализировать кривую на сцене, мы используем объект Graphics. Метод curve.draw отрисует кривую с заданным разрешением (количеством отрезков для аппроксимации).
const graphics = this.add.graphics().lineStyle(1, 0xffffff, 1);
curve.draw(graphics, 64);
Получение точки на кривой
Ключевой метод для работы с позицией на кривой — curve.getPointAt(t). Параметр `t— это нормализованное значение от 0 до 1, где 0 соответствует началу кривой, а 1 — её концу. Метод возвращает или обновляет переданный ему объектPhaser.Math.Vector2` с координатами (x, y) в этой позиции.
В примере мы сразу получаем стартовую точку кривой (t = 0) и создаём в этом месте прямоугольник (location), который будем двигать.
const point = curve.getPointAt(0);
const location = this.add.rectangle(point.x, point.y, 16, 16, 0xff00ff, 0.8);
Важно: метод getPointAt ожидает именно нормализованное значение. Если передать, например, 0.5, мы получим точку ровно посередине кривой.
Привязка движения объекта к курсору
Задача: перемещать прямоугольник по кривой в зависимости от горизонтального положения курсора мыши.
Сложность в том, что координата указателя pointer.worldX — это абсолютное значение в пикселях, а getPointAt требует значение от 0 до 1. Необходимо выполнить преобразование.
1. Мы заранее вычисляем начальную (startX) и конечную (endX) координату X нашей кривой, взяв их из массива точек.
2. Вычисляем общую длину пути по оси X: distance = endX - startX.
3. В обработчике события pointermove проверяем, находится ли курсор в пределах кривой по оси X.
4. Если да, то смещаем координату курсора относительно начала кривой: px -= startX.
5. Теперь px — это расстояние от начала кривой до курсора. Чтобы получить нормализованное значение `t, делим это расстояние на общую длину:px / distance`.
const startX = points[0];
const endX = points[points.length - 2]; // Предпоследний элемент — это X последней точки
this.input.on('pointermove', pointer => {
let px = pointer.worldX;
const distance = endX - startX;
if (px >= startX && px <= endX) {
px -= startX;
// Преобразуем координату курсора в значение от 0 до 1
curve.getPointAt(px / distance, point);
// Обновляем позицию прямоугольника
location.setPosition(point.x, point.y);
}
});
Обратите внимание: второй аргумент point в getPointAt — это ссылка на ранее созданный объект вектор. Метод обновит его координаты, что эффективнее, чем создание нового объекта каждый кадр.
Сборка сцены и конфигурация игры
Код примера является полноценной сценой Phaser 3. В методе create происходит вся инициализация, описанная выше.
Конфигурационный объект игры задаёт базовые параметры: тип рендерера, размер холста, цвет фона и корневую сцену.
class Example extends Phaser.Scene {
create () {
// ... весь код из примеров выше
}
}
const config = {
type: Phaser.AUTO,
width: 800,
height: 600,
backgroundColor: '#2d2d2d',
parent: 'phaser-example',
scene: Example // Указываем наш класс сцены
};
const game = new Phaser.Game(config);
Что попробовать дальше
Использование Phaser.Curves.Spline и метода getPointAt — это мощный и элегантный способ управления движением по сложным траекториям. Вы можете экспериментировать: привязать движение не к курсору, а к времени для создания циклической анимации, использовать curve.getPoints(quantity) для получения массива точек и перемещать объект между ними, или комбинировать несколько кривых для создания сложного маршрута. Этот механизм отлично подходит для создания траекторий снарядов, путей камеры или маршрутов патрулирования NPC.
