О чем этот пример
В игровой физике часто требуется не просто резко остановить объект, а сделать это плавно и реалистично. Пример демонстрирует, как с помощью метода `lerp` (линейной интерполяции) для вектора скорости можно создать эффект постепенного замедления объекта при приближении к цели. Это полезно для создания более "живого" поведения снарядов, врагов или игровых предметов, которые должны мягко прибывать в конечную точку, а не резко останавливаться.
Версия Phaser: код и демо в этой статье рассчитаны на Phaser 3.90.0.
Живой запуск
Ниже встроен рабочий билд примера. Оригинальный источник: GitHub.
Исходный код
class Example extends Phaser.Scene
{
distanceText;
source;
target;
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);
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).setAlpha(0.5);
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 toward the target
this.physics.moveToObject(this.source, this.target, 200);
// Interpolate velocity toward (0, 0), starting at 10px away
this.source.body.velocity.lerp(
Phaser.Math.Vector2.ZERO,
Phaser.Math.Clamp(1 - distance / 10, 0, 1)
);
}
}
}
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);
Инициализация сцены и объектов
В методе create() создаются основные игровые объекты: спрайт-источник (source) с физикой Arcade и невидимый спрайт-цель (target). Цель изначально скрыта (setAlpha(0)).
this.target = this.add.image(0, 0, 'flower').setAlpha(0);
this.source = this.physics.add.image(100, 300, 'flower');
this.distanceText = this.add.text(10, 10, 'Click to set target', { fill: 'lime' });
Обработчик клика мыши перемещает цель в указанную точку и запускает движение источника к ней с помощью this.physics.moveToObject.
this.input.on('pointerdown', (pointer) =>
{
this.target.copyPosition(pointer).setAlpha(0.5);
this.physics.moveToObject(this.source, this.target, 200);
});
Логика плавного замедления в update()
В каждом кадре метод update() рассчитывает расстояние между центром физического тела источника и целью. Это расстояние — ключевой параметр для интерполяции.
const distance = Phaser.Math.Distance.BetweenPoints(this.source.body.center, this.target);
Затем, если скорость объекта больше нуля, происходит две важные операции. Сначала this.physics.moveToObject постоянно пересчитывает вектор скорости, направляя объект к цели. Это компенсирует возможные отклонения.
if (this.source.body.speed > 0)
{
this.physics.moveToObject(this.source, this.target, 200);
Далее применяется интерполяция скорости. Метод this.source.body.velocity.lerp() плавно изменяет текущую скорость (velocity) в сторону целевого вектора. В данном случае целевой вектор — Phaser.Math.Vector2.ZERO (нулевая скорость, полная остановка).
this.source.body.velocity.lerp(
Phaser.Math.Vector2.ZERO,
Phaser.Math.Clamp(1 - distance / 10, 0, 1)
);
Коэффициент интерполяции (amount) вычисляется как Phaser.Math.Clamp(1 - distance / 10, 0, 1). Это означает:
- Когда расстояние больше 10 пикселей, значение отрицательное, и Clamp устанавливает его в 0. Интерполяция не происходит, объект движется с полной скоростью.
- При расстоянии менее 10 пикселей коэффициент становится положительным и растёт по мере приближения к цели, достигая 1 в самой точке цели. Это плавно снижает скорость до нуля.
Практическое применение и настройка
Ключевые параметры для настройки поведения:
1. **Максимальная скорость (200)**: Передаётся вторым аргументом в this.physics.moveToObject. Это желаемая скорость подлёта.
2. **Радиус начала замедления (10)**: Число 10 в выражении distance / 10. Это расстояние до цели (в пикселях), на котором начинается плавное торможение. Увеличив его, вы сделаете замедление более длинным и плавным.
// Пример: начало замедления за 50 пикселей до цели
Phaser.Math.Clamp(1 - distance / 50, 0, 1)
3. **Целевой вектор**: Вместо Phaser.Math.Vector2.ZERO можно использовать любой другой вектор. Например, чтобы объект не останавливался, а продолжал медленно дрейфовать.
// Замедление до скорости 10 пикселей/сек вправо
const targetVelocity = new Phaser.Math.Vector2(10, 0);
this.source.body.velocity.lerp(targetVelocity, 0.1);
Этот подход гораздо эффективнее и управляемее, чем простое обнуление скорости при достижении определённого расстояния.
Что попробовать дальше
Использование velocity.lerp() в связке с moveToObject() позволяет легко создавать сложное и реалистичное движение с плавным завершением. Для экспериментов попробуйте: изменить радиус начала замедления; задать конечную скорость не нулевой, а, например, медленного движения по инерции; применить интерполяцию не к скорости, а к ускорению (body.acceleration) для ещё более мягкого изменения движения.
