О чем этот пример
Создание плавного и реалистичного управления транспортным средством — частая задача в аркадных гонках. В этом примере мы разберем, как использовать Arcade Physics для управления машиной с инерцией, ограничением скорости и зависимой от скорости поворачиваемостью. Вы научитесь применять угловое ускорение, рассчитывать вектор скорости на основе вращения и динамически ограничивать маневренность, что пригодится не только в гонках, но и в любых играх с физикой транспорта или летательных аппаратов.
Версия Phaser: код и демо в этой статье рассчитаны на Phaser 3.90.0.
Живой запуск
Ниже встроен рабочий билд примера. Оригинальный источник: GitHub.
Исходный код
// velocityFromRotation() can be called like a plain function.
const VelocityFromRotation = Phaser.Physics.Arcade.ArcadePhysics.prototype.velocityFromRotation;
class Racecar extends Phaser.Physics.Arcade.Image
{
throttle = 0;
configure ()
{
this.angle = -90;
this.body.angularDrag = 120;
this.body.maxSpeed = 1024;
this.body.setSize(64, 64, true);
}
update (delta, cursorKeys)
{
const { left, right, up, down } = cursorKeys;
if (up.isDown)
{
this.throttle += 0.5 * delta;
}
else if (down.isDown)
{
this.throttle -= 0.5 * delta;
}
this.throttle = Phaser.Math.Clamp(this.throttle, -64, 1024);
if (left.isDown)
{
this.body.setAngularAcceleration(-360);
}
else if (right.isDown)
{
this.body.setAngularAcceleration(360);
}
else
{
this.body.setAngularAcceleration(0);
}
VelocityFromRotation(this.rotation, this.throttle, this.body.velocity);
this.body.maxAngular = Phaser.Math.Clamp(90 * this.body.speed / 1024, 0, 90);
}
}
class Example extends Phaser.Scene
{
preload ()
{
this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.image('soil', 'assets/textures/soil.png');
this.load.image('car', 'assets/sprites/car-yellow.png');
}
create ()
{
this.ground = this.add.tileSprite(256, 256, 512, 512, 'soil').setScrollFactor(0, 0);
this.car = new Racecar(this, 256, 512, 'car');
this.add.existing(this.car);
this.physics.add.existing(this.car);
this.car.configure();
this.cursorKeys = this.input.keyboard.createCursorKeys();
this.cameras.main.startFollow(this.car);
}
update (time, delta)
{
const { scrollX, scrollY } = this.cameras.main;
this.ground.setTilePosition(scrollX, scrollY);
this.car.update(delta, this.cursorKeys);
}
}
const config = {
type: Phaser.AUTO,
width: 512,
height: 512,
parent: 'phaser-example',
physics: {
default: 'arcade',
arcade: { debug: false }
},
scene: Example
};
const game = new Phaser.Game(config);
Основа класса Racecar: наследник Physics Image
Класс Racecar наследуется от Phaser.Physics.Arcade.Image, что сразу дает нам спрайт с физическим телом Arcade Physics.
Ключевой момент — мы выносим метод velocityFromRotation из прототипа физики в константу. Это позволяет вызывать его как обычную функцию, не обращаясь каждый раз к this.physics внутри класса машины, что делает код чище и потенциально эффективнее.
const VelocityFromRotation = Phaser.Physics.Arcade.ArcadePhysics.prototype.velocityFromRotation;
В конструкторе класса задается начальный угол и свойство throttle (газ), которое будет хранить текущее значение скорости вперед/назад.
Настройка физики в методе configure()
Метод configure() инициализирует физические параметры машины после ее добавления в мир физики.
Угол устанавливается в -90 градусов, чтобы машина изначально смотрела вверх. angularDrag создает сопротивление при вращении, делая повороты более плавными и инерционными. maxSpeed жестко ограничивает максимальную скорость. Метод setSize уменьшает размер физического тела (хитбокса) относительно спрайта, что часто используется для более справедливой коллизии.
this.angle = -90;
this.body.angularDrag = 120;
this.body.maxSpeed = 1024;
this.body.setSize(64, 64, true);
Логика управления в update()
Сердце класса — метод update(). Он обрабатывает ввод с клавиш-стрелок и обновляет состояние машины.
Клавиши ВВЕРХ и ВНИЗ изменяют значение throttle с учетом delta времени для независимости от частоты кадров. Значение затем ограничивается функцией Phaser.Math.Clamp между -64 (задний ход) и 1024 (максимальный газ).
Клавиши ВЛЕВО и ВПРАВО задают угловое ускорение (setAngularAcceleration), заставляя машину вращаться. Когда клавиши отпущены, ускорение сбрасывается в ноль, но angularDrag постепенно остановит вращение.
if (up.isDown) { this.throttle += 0.5 * delta; }
if (down.isDown) { this.throttle -= 0.5 * delta; }
this.throttle = Phaser.Math.Clamp(this.throttle, -64, 1024);
if (left.isDown) { this.body.setAngularAcceleration(-360); }
else if (right.isDown) { this.body.setAngularAcceleration(360); }
else { this.body.setAngularAcceleration(0); }
Связь скорости, направления и поворачиваемости
Самая важная строка в update() преобразует значение газа (throttle) и текущий угол вращения в вектор линейной скорости.
Функция VelocityFromRotation принимает угол (this.rotation), величину скорости (this.throttle) и объект, в который нужно записать результат (this.body.velocity). Это физически корректный способ задания движения вперед по направлению взгляда объекта.
VelocityFromRotation(this.rotation, this.throttle, this.body.velocity);
Следующая строка делает управление более реалистичным: максимальная угловая скорость (maxAngular) зависит от текущей линейной скорости (this.body.speed). Чем быстрее едет машина, тем медленнее она может поворачивать. Это вычисляется как доля от 90 градусов в секунду и также ограничивается.
this.body.maxAngular = Phaser.Math.Clamp(90 * this.body.speed / 1024, 0, 90);
Интеграция в сцену и камера
В сцене Example создается экземпляр Racecar, добавляется в дисплейный список и в мир физики. Камера начинает следовать за машиной с помощью startFollow.
this.car = new Racecar(this, 256, 512, 'car');
this.add.existing(this.car);
this.physics.add.existing(this.car);
this.cameras.main.startFollow(this.car);
В update сцены вызывается update машины, передавая delta и объект с клавишами. Также здесь реализован простой параллакс для фона (soil): текстура тайлспрайта смещается относительно позиции камеры, создавая иллюзию движения.
const { scrollX, scrollY } = this.cameras.main;
this.ground.setTilePosition(scrollX, scrollY);
this.car.update(delta, this.cursorKeys);
Что попробовать дальше
Этот пример демонстрирует мощь Arcade Physics для создания satisfying-геймплея аркадного вождения. Ключевые приемы — использование velocityFromRotation для движения вперед, углового ускорения для поворотов и динамического ограничения maxAngular для реализма. Для экспериментов попробуйте: изменить значения angularDrag и maxSpeed для другого "веса" машины; добавить частицы или след от шин при дрифте; реализовать автоматическое выравнивание машины при отпускании газа или использовать другую форму хитбокса через setCircle.
