О чем этот пример
Реализация плавного и естественного движения объектов — ключевой элемент геймдизайна. В примере из официальной документации Phaser демонстрируется техника движения спрайта к цели с постепенным замедлением. Эта статья разберет код примера и покажет, как использовать комбинацию методов `physics.moveToObject()` и `Phaser.Math.SmoothStep()` для создания реалистичного движения, которое останавливается точно в целевой точке без резких рывков. Понимание этого подхода поможет вам создавать более отзывчивый и приятный геймплей.
Версия 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('flower', 'assets/sprites/flower-exo.png');
this.load.image('cursor', 'assets/sprites/drawcursor.png');
}
create ()
{
this.target = this.add.image(0, 0, 'flower').setAlpha(0.4);
this.source = this.physics.add.image(100, 300, 'flower');
this.distanceText = this.add.text(10, 10, 'Click to set target', { fill: 'lime' });
this.input.on('pointerdown', (pointer) =>
{
this.target.copyPosition(pointer).setVisible(true);
this.physics.moveToObject(this.source, this.target, 200);
});
}
update ()
{
const distance = Phaser.Math.Distance.BetweenPoints(this.source.body.center, this.target);
this.distanceText.setText(`Distance: ${distance.toFixed(3)} Speed: ${this.source.body.speed.toFixed(3)}`);
if (this.source.body.speed > 0)
{
// Set a maximum velocity
this.physics.moveToObject(this.source, this.target, 200);
// Scale down based on distance, starting from 20px away
this.source.body.velocity.scale(
Phaser.Math.SmoothStep(distance, 0, 20)
);
if (distance < 1)
{
// Close enough
this.source.body.reset(this.target.x, this.target.y);
this.source.body.debugBodyColor = 0xff0000;
}
else
{
this.source.body.debugBodyColor = 0xffff00;
}
}
}
}
const config = {
type: Phaser.AUTO,
width: 800,
height: 600,
parent: 'phaser-example',
physics: {
default: 'arcade',
arcade: {
debug: true
}
},
scene: Example
};
const game = new Phaser.Game(config);
Основная идея примера
В примере реализована следующая логика: при клике мыши в любую точку экрана физический спрайт source начинает движение к этой цели. Когда объект приближается к цели, его скорость плавно уменьшается, обеспечивая точную остановку. Для визуализации процесса включен режим отладки физики (debug: true), а также отображается текущее расстояние до цели и скорость объекта.
Основные компоненты сцены:
- `this.target` — нефизический спрайт-цель с прозрачностью (призрак).
- `this.source` — физический спрайт, который движется.
- Текстовое поле для отображения дистанции и скорости.
- Обработчик клика для установки новой цели.
Инициализация и настройка физики
В методе create() происходит настройка всех игровых объектов и физического движка Arcade.
this.target = this.add.image(0, 0, 'flower').setAlpha(0.4);
this.source = this.physics.add.image(100, 300, 'flower');
Ключевой момент — объект source создается через this.physics.add.image(), что автоматически добавляет ему Arcade Physics тело (body), способное к движению. Объект target создается обычным методом this.add.image() и физического тела не имеет. Именно к его координатам будет двигаться source.
Конфигурация игры включает настройку физики с включенным режимом отладки, который подсвечивает физические тела.
physics: {
default: 'arcade',
arcade: {
debug: true
}
}
При клике мыши позиция цели обновляется, и для источника вызывается this.physics.moveToObject().
this.input.on('pointerdown', (pointer) => {
this.target.copyPosition(pointer).setVisible(true);
this.physics.moveToObject(this.source, this.target, 200);
});
Метод moveToObject() задает телу source.body вектор скорости по направлению к цели. Параметр 200 — это желаемая скорость в пикселях в секунду.
Логика плавного замедления в update()
Вся магия плавной остановки происходит в методе update(), который вызывается каждый кадр.
const distance = Phaser.Math.Distance.BetweenPoints(this.source.body.center, this.target);
Сначала вычисляется текущее расстояние между центром физического тела (this.source.body.center) и целью (this.target).
Затем, если скорость тела больше нуля (this.source.body.speed > 0), применяется алгоритм замедления.
this.physics.moveToObject(this.source, this.target, 200);
Этот вызов на каждом кадре постоянно корректирует вектор скорости, направляя объект к текущей позиции цели (на случай, если цель движется).
Самый важный момент — масштабирование вектора скорости (velocity) с помощью функции плавного шага Phaser.Math.SmoothStep().
this.source.body.velocity.scale(
Phaser.Math.SmoothStep(distance, 0, 20)
);
Функция SmoothStep(distance, 0, 20) возвращает значение от 0 до 1. Когда distance (расстояние) больше 20, результат близок к 1, и скорость не меняется. Когда расстояние становится меньше 20 пикселей, результат начинает плавно уменьшаться к 0, тем самым замедляя объект. Масштабирование вектора скорости на этот множитель и создает эффект плавного торможения.
Финализация движения и обработка столкновений
Когда объект оказывается достаточно близко к цели, его движение нужно завершить. В примере это происходит, когда расстояние становится меньше 1 пикселя.
if (distance < 1) {
// Close enough
this.source.body.reset(this.target.x, this.target.y);
this.source.body.debugBodyColor = 0xff0000;
}
Метод this.source.body.reset() мгновенно перемещает физическое тело в указанные координаты и обнуляет его скорость и ускорение. Это стандартный способ "телепортации" физического объекта в Arcade Physics. Для наглядности цвет отладки тела меняется на красный (0xff0000).
Если же объект еще в пути, его тело подсвечивается желтым цветом (0xffff00).
Важное замечание: в этом примере не используется коллизия (collide или overlap), так как цель не является физическим телом. Вся логика строится на расчете расстояния между двумя точками.
Что попробовать дальше
Пример демонстрирует элегантный способ комбинирования высокоуровневого метода physics.moveToObject() и математической функции SmoothStep для создания движения с интеллектуальным замедлением. Этот паттерн применим в самых разных ситуациях: от следования врагов за игроком до перемещения интерфейсных элементов.
**Идеи для экспериментов:**
1. Замените SmoothStep на другие функции интерполяции из Phaser.Math, например, Linear или Cubic.Out, чтобы изменить характер замедления.
2. Сделайте цель подвижной, например, привязанной к другому спрайту, и наблюдайте, как объект будет плавно следовать за ней.
3. Добавьте проверку столкновений (this.physics.add.collider()) для движущегося объекта с другими статическими телами, чтобы создать более сложную навигацию с обходом препятствий.
