О чем этот пример
При создании игр часто возникает задача, когда объект должен двигаться по сложной траектории (например, по пути твина или патрульному маршруту), но при этом полноценно участвовать в физических взаимодействиях — сталкиваться с другими телами, отталкивать их, передавать импульс. Обычный спрайт с твином не вызовет физической реакции у других тел, а физическое тело с установленной скоростью не сможет плавно следовать по сложной кривой. В этой статье мы разберем мощный, но неочевидный метод Arcade Physics `setDirectControl()`, который решает эту проблему, позволяя напрямую управлять позицией физического тела, сохраняя все коллизии. Этот подход полезен для создания движущихся платформ, транспортеров, вражеских патрулей или любых объектов, чье движение задано внешней логикой, но которые должны влиять на игровой мир.
Версия 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('master', 'assets/sprites/master.png');
this.load.image('mushroom', 'assets/sprites/mushroom16x16.png');
}
create ()
{
this.master = this.physics.add.image(100, 300, 'master');
this.master.setDirectControl();
this.master.setImmovable();
this.tweens.add({
targets: this.master,
x: 700,
duration: 3000,
ease: 'sine.inout',
repeat: -1,
yoyo: true
});
this.debug = this.add.text(10, 10, '', { font: '16px Courier', fill: '#00ff00' });
this.mushrooms = [];
for (let i = 0; i < 64; i++)
{
const x = Phaser.Math.Between(0, 800);
const y = Phaser.Math.Between(-1900, 0);
const mushroom = this.physics.add.image(x, y, 'mushroom');
mushroom.body.setBounce(1);
mushroom.body.setMaxVelocity(2000, 2000);
mushroom.setCollideWorldBounds(true);
this.mushrooms.push(mushroom);
}
this.physics.world.setBounds(0, -2000, 800, 2600);
this.physics.add.collider(this.master, this.mushrooms);
}
update ()
{
const block = this.master;
this.debug.setText([
'velocity.x: ' + block.body.velocity.x,
'velocity.y: ' + block.body.velocity.y
]);
}
}
const config = {
type: Phaser.AUTO,
width: 800,
height: 600,
parent: 'phaser-example',
physics: {
default: 'arcade',
arcade: {
debug: true,
gravity: { y: 50 },
}
},
scene: Example
};
const game = new Phaser.Game(config);
Суть метода setDirectControl()
Метод setDirectControl() — это специфическая функция Arcade Physics для физических тел (Arcade Physics Body). Его вызов переключает тело в особый режим, при котором его позиция (`x,y) может изменяться напрямую, минуя стандартные расчеты скорости и ускорения движка физики. Обычно Arcade Physics на каждом кадре вычисляет новую позицию тела на основе его текущей скорости, ускорения и гравитации.setDirectControl()` отключает этот автоматический расчет для данного конкретного тела.
Однако, и это ключевой момент, тело продолжает оставаться частью физического мира. Система коллизий по-прежнему работает: она будет обнаруживать пересечения с другими телами и вызывать соответствующие события (например, collider). Более того, при столкновении это тело сможет передавать импульс и влиять на движение других объектов, что невозможно при использовании обычного спрайта.
this.master = this.physics.add.image(100, 300, 'master');
this.master.setDirectControl();
this.master.setImmovable();
Сценарий из примера: Движущаяся платформа и грибы
В предоставленном примере создается классическая ситуация: движущаяся платформа (master) и множество падающих объектов (mushrooms). Платформа должна циклично двигаться из стороны в сторону, в то время как на нее будут падать грибы, отскакивать и сталкиваться друг с другом.
1. **Платформа (master)**: Создается как физическое изображение. Сразу же для ее тела вызывается setDirectControl(), что позволяет управлять ее позицией через твин. Также вызывается setImmovable(true). Это важно: поскольку позиция платформы задана явно, мы не хотим, чтобы столкновения с грибами сдвигали ее. Метод setImmovable() делает тело невосприимчивым к силам от других тел, но не отменяет сами коллизии.
2. **Движение платформы**: Для анимации движения используется твин Phaser. Он плавно меняет свойство `x` спрайта между значениями 100 и 700.
this.tweens.add({
targets: this.master,
x: 700,
duration: 3000,
ease: 'sine.inout',
repeat: -1,
yoyo: true
});
3. **Грибы (mushrooms)**: Создаются 64 физических спрайта в случайных позициях в верхней части расширенного мира. Для них настраиваются физические свойства: упругость (setBounce(1) для идеального отскока), максимальная скорость и включение столкновения с границами мира.
4. **Мир и коллизия**: Границы физического мира расширяются вверх, чтобы грибам было откуда падать. Затем создается коллайдер между платформой (master) и массивом грибов (mushrooms). Именно благодаря этому грибы будут отскакивать от движущейся платформы.
this.physics.world.setBounds(0, -2000, 800, 2600);
this.physics.add.collider(this.master, this.mushrooms);
Ключевые наблюдения и отладка
В методе update() выводится отладочная информация о скорости тела платформы. Это демонстрирует важный аспект работы setDirectControl().
const block = this.master;
this.debug.setText([
'velocity.x: ' + block.body.velocity.x,
'velocity.y: ' + block.body.velocity.y
]);
Вы увидите, что значения velocity.x и velocity.y остаются равными нулю, несмотря на то, что спрайт перемещается по экрану. Это прямое доказательство того, что движок физики не рассчитывает позицию платформы на основе скорости. Позиция меняется твином, а физический движок лишь проверяет эту новую позицию на предмет коллизий с другими телами в каждом кадре.
Без setDirectControl() попытка изменить `xилиy` физического тела напрямую (через твин или присваивание) в следующем же кадре была бы перезаписана расчетами движка, основанными на скорости тела, что привело бы к неожиданному или отсутствующему движению.
Когда использовать этот подход?
Используйте setDirectControl() в сочетании с setImmovable() для следующих типов объектов:
* **Движущиеся платформы и транспортеры:** Как в примере. Объект движется по заданному пути и должен подбрасывать, толкать или переносить игрока и другие предметы.
* **Управляемые "кинетические" препятствия:** Части механизмов, которые движутся по сложной траектории (например, маятник, вращающаяся платформа, двигающаяся стена) и должны наносить урон или отталкивать игрока при столкновении.
* **Объекты, чье движение полностью описано анимацией:** Если у вас есть спрайт со сложной frame-based анимацией, которая включает в себя смещение, и это смещение должно быть физически значимым.
**Важное ограничение:** Так как тело не имеет скорости в терминах движка, оно не будет передавать свою "кинетическую энергию" другим телам на основе скорости. Эффект столкновения будет определяться свойствами второго тела (массой, упругостью) и общими правилами разрешения коллизий Arcade Physics. Для симуляции мощного толчка вам, возможно, придется вручную задавать скорость сталкивающемуся телу в обработчике коллизии.
Что попробовать дальше
Метод setDirectControl() — это элегантное решение для совмещения детерминированного, скриптового движения с физическим взаимодействием в Phaser. Он открывает путь к созданию сложных и интерактивных элементов окружения. Для экспериментов попробуйте: изменить траекторию движения платформы на круговую с помощью математики или другого твина; сделать платформу разрушаемой после N столкновений; или создать цепь связанных платформ, где одна ведет за собой другие через коллайдеры. Помните, что для динамических объектов, которые должны реагировать на удары, setImmovable() использовать не стоит.
