О чем этот пример
Создание плавного и естественного движения объектов — ключ к приятному геймплею. Часто простого задания скорости недостаточно, нужны плавные переходы, замедления и ускорения. В этой статье мы разберем продвинутый пример из официальной документации Phaser, где корабль плавно перемещается между случайными точками, используя твининг скорости и геометрию. Вы научитесь комбинировать физику Arcade, систему твинов и геометрические объекты для создания сложных паттернов движения.
Версия Phaser: код и демо в этой статье рассчитаны на Phaser 3.90.0.
Живой запуск
Ниже встроен рабочий билд примера. Оригинальный источник: GitHub.
Исходный код
class Ship extends Phaser.Physics.Arcade.Sprite
{
constructor (scene, x, y, points)
{
super(scene, x, y, 'ship', 2);
scene.add.existing(this);
scene.physics.add.existing(this);
this.setScale(2);
this.play('thrust');
this.speed = 150;
this.points = points;
this.target;
this.targetIndex = -1;
this.isSeeking = true;
this.seek();
}
seek ()
{
// Pick a random target point
const entry = Phaser.Utils.Array.GetRandom(this.points);
this.target = entry;
this.isSeeking = false;
this.scene.tweens.add({
targets: this.body.velocity,
x: 0,
y: 0,
ease: 'Linear',
duration: 500,
onComplete: function (tween, targets, ship)
{
ship.isSeeking = true;
ship.scene.tweens.add({
targets: ship,
speed: 150,
delay: 500,
ease: 'Sine.easeOut',
duration: 1000
});
},
onCompleteParams: [ this ]
});
}
preUpdate (time, delta)
{
super.preUpdate(time, delta);
// Is the ship within the radius of the target?
if (this.target.contains(this.x, this.y))
{
this.seek();
}
else if (this.isSeeking)
{
const angle = Math.atan2(this.target.y - this.y, this.target.x - this.x);
this.scene.physics.velocityFromRotation(angle, this.speed, this.body.velocity);
}
}
}
class Example extends Phaser.Scene
{
preload ()
{
this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.spritesheet('ship', 'assets/games/lazer/ship.png', { frameWidth: 16, frameHeight: 24 });
}
create ()
{
this.anims.create(
{
key: 'thrust',
frames: this.anims.generateFrameNumbers('ship', { frames: [ 2, 7 ] }),
frameRate: 20,
repeat: -1
});
const points = [
new Phaser.Geom.Circle(100, 100, 32),
new Phaser.Geom.Circle(400, 100, 32),
new Phaser.Geom.Circle(700, 100, 32),
new Phaser.Geom.Circle(100, 300, 32),
new Phaser.Geom.Circle(400, 300, 32),
new Phaser.Geom.Circle(700, 300, 32),
new Phaser.Geom.Circle(100, 500, 32),
new Phaser.Geom.Circle(400, 500, 32),
new Phaser.Geom.Circle(700, 500, 32)
];
const debug = this.add.graphics();
debug.lineStyle(1, 0x00ff00);
for (const p of points)
{
debug.strokeCircleShape(p);
}
this.graphics = this.add.graphics();
this.ship = new Ship(this, 400, 300, points);
}
update ()
{
this.graphics.clear();
this.graphics.fillStyle(0xff0000);
this.graphics.fillRect(this.ship.target.x, this.ship.target.y, 4, 4);
}
}
const config = {
type: Phaser.AUTO,
parent: 'phaser-example',
width: 800,
height: 600,
pixelArt: true,
physics: {
default: 'arcade',
arcade: {
debug: true,
gravity: { y: 0 }
}
},
scene: Example
};
const game = new Phaser.Game(config);
Анализ структуры кода: Ship и Example
Пример разделен на два основных класса: Ship (наследник Phaser.Physics.Arcade.Sprite) и Example (наследник Phaser.Scene). Корабль — это физический спрайт, который самостоятельно управляет своим движением, выбирая цели и твиня скорость. Основная сцена отвечает за создание анимации, целевых точек и визуализацию.
Ключевые моменты конфигурации игры:
physics: {
default: 'arcade',
arcade: {
debug: true,
gravity: { y: 0 }
}
}
Здесь активируется физика Arcade без гравитации и включается режим отладки, который рисует хитбоксы.
Механика поиска цели и расчета скорости
В методе preUpdate класса Ship содержится основная логика движения. Если корабль достиг цели (проверка через this.target.contains(this.x, this.y)), он запускает новый поиск. Если же он находится в режиме преследования (this.isSeeking), то рассчитывается угол до цели и задается скорость.
Расчет угла и установка скорости:
const angle = Math.atan2(this.target.y - this.y, this.target.x - this.x);
this.scene.physics.velocityFromRotation(angle, this.speed, this.body.velocity);
Функция velocityFromRotation — это удобный метод Arcade Physics для преобразования угла и числовой скорости в вектор скорости тела (body.velocity).
Магия твининга: плавные остановки и старты
Самый интересный аспект примера — использование системы твинов Phaser для анимации не спрайта, а его скорости. В методе seek() при выборе новой цели сначала плавно обнуляется текущая скорость корабля.
Твининг скорости до нуля:
this.scene.tweens.add({
targets: this.body.velocity,
x: 0,
y: 0,
ease: 'Linear',
duration: 500,
onComplete: ...
});
Обратите внимание: targets — это this.body.velocity, объект с полями `xиy. Твин плавно меняет их значения на ноль за 500 мс. После завершения этого твина, в колбэкеonComplete, запускается второй твин, который плавно возвращает свойствоship.speed` к исходному значению 150, создавая эффект плавного разгона.
Работа с геометрией: цели как области
Цели для корабля — это не просто точки, а объекты Phaser.Geom.Circle. Это позволяет проверять достижение цели не по точному совпадению координат, а по попаданию в заданную область (радиус 32 пикселя).
Создание массива целей (кругов) и их отрисовка для отладки:
const points = [
new Phaser.Geom.Circle(100, 100, 32),
// ... другие круги
];
const debug = this.add.graphics();
debug.lineStyle(1, 0x00ff00);
for (const p of points) {
debug.strokeCircleShape(p);
}
Метод contains() объекта Circle проверяет, находится ли переданная точка внутри его границ. Это делает механику движения более надежной и визуально приятной.
Визуализация и отладка в реальном времени
Класс Example в методе update() занимается дополнительной визуализацией. Он очищает графический слой и рисует маленький красный квадрат в текущей целевой точке корабля.
Код отрисовки цели:
update ()
{
this.graphics.clear();
this.graphics.fillStyle(0xff0000);
this.graphics.fillRect(this.ship.target.x, this.ship.target.y, 4, 4);
}
Этот прием крайне полезен при отладке сложного движения, AI или путей. Вы всегда видите, куда именно движется объект. Включенный флаг debug: true в конфиге физики также рисует контуры физических тел.
Что попробовать дальше
Комбинация физики Arcade, системы твинов и геометрических объектов Phaser открывает огромные возможности для создания динамичного и "живого" движения. Вы можете экспериментировать: измените ease-функции в твинах для другого ощущения инерции, замените случайный выбор цели на движение по патрульному маршруту или используйте Phaser.Geom.Rectangle для прямоугольных зон интереса. Попробуйте привязать скорость корабля к расстоянию до цели для создания эффекта "осторожного" приближения.
