О чем этот пример
Создание сложных траекторий движения для игровых объектов — частая задача разработчика. Phaser 3 предлагает мощный инструментарий `Phaser.Curves.Path` для описания путей любой сложности: от прямых линий до сплайнов и эллипсов. В этом примере мы рассмотрим, как заставить спрайт двигаться по заранее заданному пути с плавным контролем, сочетая это с динамической физикой других объектов. Это полезно для создания патрулирующих врагов, летающих платформ, кат-сцен или движения камеры.
Версия Phaser: код и демо в этой статье рассчитаны на Phaser 3.90.0.
Живой запуск
Ниже встроен рабочий билд примера. Оригинальный источник: GitHub.
Исходный код
class Example extends Phaser.Scene
{
preload ()
{
this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.image('bg', 'assets/skies/gradient8.png');
this.load.image('master', 'assets/sprites/master.png');
this.load.image('bubble', 'assets/particles/bubble.png');
}
create ()
{
this.add.image(400, 300, 'bg');
const graphics = this.add.graphics();
const path = new Phaser.Curves.Path(50, 500);
path.splineTo([ 164, 446, 274, 542, 412, 457, 522, 541, 664, 464 ]);
path.lineTo(700, 300);
path.lineTo(600, 350);
path.ellipseTo(200, 100, 100, 250, false, 0);
path.cubicBezierTo(222, 119, 308, 107, 208, 368);
path.ellipseTo(60, 60, 0, 360, true);
graphics.lineStyle(2, 0xffffff, 0.5);
path.draw(graphics);
const position = path.getPoint(0);
const master = this.physics.add.image(position.x, position.y, 'master');
master.setDirectControl();
master.setImmovable();
this.counter = this.tweens.addCounter({
from: 0,
to: 1,
ease: 'linear',
duration: 8000,
repeat: -1,
yoyo: true,
onUpdate: tween =>
{
const position = path.getPoint(tween.getValue());
master.setPosition(position.x, position.y);
}
});
const bubbles = [];
for (let i = 0; i < 64; i++)
{
const x = Phaser.Math.Between(0, 800);
const y = Phaser.Math.Between(-1500, 0);
const bubble = this.physics.add.image(x, y, 'bubble');
bubble.setScale(0.5);
bubble.setBounce(1);
bubble.setDrag(5);
bubble.setVelocityX(Phaser.Math.Between(-80, 80));
bubble.setVelocityY(Phaser.Math.Between(10, 50));
bubble.setMaxVelocity(700, 700);
bubble.setCollideWorldBounds(true);
bubbles.push(bubble);
}
this.physics.world.setBounds(0, -1500, 800, 2100);
this.physics.add.collider(master, bubbles);
}
}
const config = {
type: Phaser.AUTO,
width: 800,
height: 600,
parent: 'phaser-example',
physics: {
default: 'arcade',
arcade: {
debug: false,
gravity: { y: 50 },
}
},
scene: Example
};
const game = new Phaser.Game(config);
Создание и визуализация сложного пути
Класс Phaser.Curves.Path позволяет построить последовательный путь из различных сегментов. Начальная точка задаётся в конструкторе, а затем к пути добавляются элементы.
const path = new Phaser.Curves.Path(50, 500);
Далее мы используем методы пути для создания сложной траектории. Метод .splineTo() создаёт плавную кривую через массив контрольных точек. .lineTo() ведёт прямую линию к указанной точке. .ellipseTo() и .cubicBezierTo() добавляют эллиптические и кубические Безье-сегменты соответственно. Флаг в .ellipseTo() определяет, рисуется ли эллипс по или против часовой стрелки.
path.splineTo([ 164, 446, 274, 542, 412, 457, 522, 541, 664, 464 ]);
path.lineTo(700, 300);
path.ellipseTo(200, 100, 100, 250, false, 0);
Чтобы увидеть путь на экране, мы рисуем его с помощью объекта Graphics. Устанавливаем стиль линии и вызываем метод .draw().
const graphics = this.add.graphics();
graphics.lineStyle(2, 0xffffff, 0.5);
path.draw(graphics);
Плавное движение спрайта по пути с помощью Tween
Для движения объекта по пути используется твин-счётчик (Tween). Его задача — генерировать плавно меняющееся значение от 0 до 1, которое соответствует прогрессу движения от начала до конца пути.
this.counter = this.tweens.addCounter({
from: 0,
to: 1,
ease: 'linear',
duration: 8000,
repeat: -1,
yoyo: true
});
Ключевой параметр — onUpdate. Это callback-функция, которая вызывается на каждом кадре обновления твина. Внутри неё мы получаем текущее значение прогресса через tween.getValue(), передаём его в метод пути path.getPoint(), который возвращает конкретные координаты (x, y) на кривой для этого прогресса. Затем эти координаты применяются к спрайту.
onUpdate: tween =>
{
const position = path.getPoint(tween.getValue());
master.setPosition(position.x, position.y);
}
Спрайт master создаётся как физический объект (this.physics.add.image) в начальной точке пути. Методы setDirectControl() и setImmovable() отключают для него влияние физического движка (гравитацию, столкновения как динамического тела), позволяя управлять позицией вручную через setPosition.
const master = this.physics.add.image(position.x, position.y, 'master');
master.setDirectControl();
master.setImmovable();
Создание динамического фона с физическими частицами
Чтобы мир не выглядел статичным, добавим физические объекты — пузыри. Они создаются в цикле со случайными начальными координатами за пределами видимой области (вверху).
const bubble = this.physics.add.image(x, y, 'bubble');
Каждому пузырю настраиваются физические свойства:
- setScale(0.5) — уменьшает размер.
- setBounce(1) — делает отскок абсолютно упругим.
- setDrag(5) — добавляет сопротивление движению, делая его более "тяжёлым".
- setVelocityX/Y() — задаёт начальную случайную скорость.
- setMaxVelocity(700, 700) — ограничивает максимальную скорость.
- setCollideWorldBounds(true) — включает столкновение с границами мира.
bubble.setVelocityX(Phaser.Math.Between(-80, 80));
bubble.setVelocityY(Phaser.Math.Between(10, 50));
Границы физического мира расширяются далеко за пределы экрана, чтобы пузыри могли свободно двигаться и падать сверху. Это создаёт иллюзию бесконечного пространства.
this.physics.world.setBounds(0, -1500, 800, 2100);
Наконец, коллайдер this.physics.add.collider(master, bubbles) регистрирует столкновения между главным спрайтом и всеми пузырями. Несмотря на то что master является Immovable, столкновения всё равно будут обрабатываться, и пузыри будут от него отталкиваться.
Конфигурация игры и физики
Базовая конфигурация игры включает настройку физического движка Arcade. Здесь важно отметить параметр gravity: { y: 50 }, который заставляет пузыри постепенно падать вниз, создавая естественное движение.
physics: {
default: 'arcade',
arcade: {
debug: false,
gravity: { y: 50 },
}
}
Что попробовать дальше
Комбинирование предопределённых путей движения и динамической физики Arcade — мощный приём для создания живых, интерактивных сцен. Главный спрайт, двигаясь по сложной траектории, становится частью физического мира и взаимодействует с другими объектами.
**Идеи для экспериментов:**
1. Измените параметры твина: попробуйте другие функции ease (например, 'sine.inout') для нелинейного движения по пути.
2. Сделайте движение пузырей более сложным: добавьте случайные импульсы через setAcceleration или заставьте их реагировать на курсор мыши.
3. Используйте path.getTangent() в onUpdate для автоматического вращения спрайта master вдоль касательной к пути, создав эффект управления кораблём.
4. Создайте несколько разных путей и несколько объектов, движущихся по ним с разной скоростью и фазами.
