О чем этот пример
В играх часто требуется придать объекту движение в определённом направлении. Например, выстрелить снарядом или подбросить персонажа. В этом примере мы разберём, как в Phaser 3 и физическом движке Arcade можно легко и эффективно преобразовать угол поворота в вектор скорости для физического тела. Этот подход лежит в основе механик стрельбы, метания и прыжков под углом. Мы создадим интерактивную пушку, которая поворачивается за курсором мыши, и при выстреле будет запускать спрайт с заданной начальной скоростью по траектории выстрела, с учётом гравитации.
Версия 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('backdrop', 'assets/pics/platformer-backdrop.png');
this.load.image('cannon_head', 'assets/tests/timer/cannon_head.png');
this.load.image('cannon_body', 'assets/tests/timer/cannon_body.png');
this.load.spritesheet('chick', 'assets/sprites/chick.png', { frameWidth: 16, frameHeight: 18 });
}
create ()
{
this.anims.create({ key: 'fly', frames: this.anims.generateFrameNumbers('chick', [ 0, 1, 2, 3 ]), frameRate: 5, repeat: -1 });
this.add.image(320, 256, 'backdrop').setScale(2);
const cannonHead = this.add.image(130, 416, 'cannon_head').setDepth(1);
const cannon = this.add.image(130, 464, 'cannon_body').setDepth(1);
const chick = this.physics.add.sprite(cannon.x, cannon.y - 50, 'chick').setScale(2);
const graphics = this.add.graphics({ lineStyle: { width: 10, color: 0xffdd00, alpha: 0.5 } });
const line = new Phaser.Geom.Line();
chick.disableBody(true, true);
let angle = 0;
this.input.on('pointermove', (pointer) =>
{
angle = Phaser.Math.Angle.BetweenPoints(cannon, pointer);
cannonHead.rotation = angle;
Phaser.Geom.Line.SetToAngle(line, cannon.x, cannon.y - 50, angle, 128);
graphics.clear().strokeLineShape(line);
});
this.input.on('pointerup', () =>
{
chick.enableBody(true, cannon.x, cannon.y - 50, true, true);
chick.play('fly');
this.physics.velocityFromRotation(angle, 600, chick.body.velocity);
});
}
}
const config = {
type: Phaser.AUTO,
width: 640,
height: 512,
parent: 'phaser-example',
pixelArt: true,
physics: {
default: 'arcade',
arcade: {
gravity: { y: 300 }
}
},
scene: Example
};
const game = new Phaser.Game(config);
Подготовка сцены и загрузка ресурсов
В методе preload мы загружаем все необходимые изображения. Обратите внимание на load.spritesheet для спрайта цыплёнка: мы загружаем лист с несколькими кадрами, чтобы позже создать анимацию.
this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.image('backdrop', 'assets/pics/platformer-backdrop.png');
this.load.image('cannon_head', 'assets/tests/timer/cannon_head.png');
this.load.image('cannon_body', 'assets/tests/timer/cannon_body.png');
this.load.spritesheet('chick', 'assets/sprites/chick.png', { frameWidth: 16, frameHeight: 18 });
В create первым делом создаётся анимация полёта из кадров спрайтшита. Затем на сцену добавляются статичные изображения фона и частей пушки. Важный момент: ствол пушки (cannonHead) получает метод .setDepth(1), чтобы он отрисовывался поверх тела пушки.
this.anims.create({ key: 'fly', frames: this.anims.generateFrameNumbers('chick', [ 0, 1, 2, 3 ]), frameRate: 5, repeat: -1 });
this.add.image(320, 256, 'backdrop').setScale(2);
const cannonHead = this.add.image(130, 416, 'cannon_head').setDepth(1);
const cannon = this.add.image(130, 464, 'cannon_body').setDepth(1);
Создание физического тела и визуализация прицела
Цыплёнок создаётся не как обычный спрайт, а как физическое тело Arcade с помощью this.physics.add.sprite. Это сразу добавляет ему свойство body для управления скоростью и столкновениями. Изначально тело отключается методом disableBody, чтобы цыплёнок не падал под действием гравитации до выстрела.
const chick = this.physics.add.sprite(cannon.x, cannon.y - 50, 'chick').setScale(2);
chick.disableBody(true, true);
Для наглядности мы создаём графический объект (graphics) и геометрическую линию (line), чтобы рисовать линию прицеливания от пушки до курсора.
const graphics = this.add.graphics({ lineStyle: { width: 10, color: 0xffdd00, alpha: 0.5 } });
const line = new Phaser.Geom.Line();
Поворот пушки и обновление линии прицела
Обработчик события pointermove обновляет угол и визуальные элементы в реальном времени при движении мыши.
1. Phaser.Math.Angle.BetweenPoints вычисляет угол (в радианах) между центром пушки и текущей позицией курсора.
2. Этот угол присваивается вращению ствола пушки (cannonHead.rotation).
3. Phaser.Geom.Line.SetToAngle перестраивает нашу геометрическую линию: задаёт её начало, угол и длину.
4. graphics.clear().strokeLineShape(line) стирает предыдущую линию и рисует новую.
this.input.on('pointermove', (pointer) => {
angle = Phaser.Math.Angle.BetweenPoints(cannon, pointer);
cannonHead.rotation = angle;
Phaser.Geom.Line.SetToAngle(line, cannon.x, cannon.y - 50, angle, 128);
graphics.clear().strokeLineShape(line);
});
Выстрел: преобразование угла в скорость
В момент клика (pointerup) происходит "выстрел".
1. Цыплёнок активируется в начальной позиции (у дула пушки) с помощью enableBody.
2. Запускается анимация его полёта.
3. Ключевой метод this.physics.velocityFromRotation принимает три аргумента: угол (в радианах), величину скорости (пикселей в секунду) и объект скорости тела (chick.body.velocity). Метод напрямую вычисляет и присваивает компоненты скорости velocity.x и velocity.y для движения под заданным углом.
this.input.on('pointerup', () => {
chick.enableBody(true, cannon.x, cannon.y - 50, true, true);
chick.play('fly');
this.physics.velocityFromRotation(angle, 600, chick.body.velocity);
});
После этого физический движок Arcade берёт управление на себя: к телу цыплёнка применяется заданная начальная скорость и глобальная гравитация (gravity: { y: 300 }), в результате чего мы получаем красивую баллистическую траекторию.
Настройка физики в конфигурации игры
Чтобы всё работало, необходимо активировать физический плагин Arcade в конфигурации игры. Обратите внимание на параметр pixelArt: true, который включает сглаживание текстур для пиксельной графики.
const config = {
type: Phaser.AUTO,
width: 640,
height: 512,
parent: 'phaser-example',
pixelArt: true,
physics: {
default: 'arcade',
arcade: {
gravity: { y: 300 }
}
},
scene: Example
};
Что попробовать дальше
Использование physics.velocityFromRotation — это самый прямой и производительный способ задать движение физического тела по вектору в Phaser 3 Arcade. Этот метод идеально подходит для снарядов, прыжков под углом или запуска объектов с катапульты.
**Идеи для экспериментов:**
1. Измените величину скорости (второй аргумент) в зависимости от времени удержания кнопки мыши, чтобы реализовать "силу" выстрела.
2. Добавьте несколько "снарядов" с созданием новых спрайтов при каждом выстреле и ограничением их количества на сцене.
3. Попробуйте применить velocityFromRotation не к скорости (velocity), а к ускорению (acceleration) тела, чтобы получить другой тип движения.
