О чем этот пример
Механика перетаскивания объектов — частый гость в играх-головоломках, конструкторах и казуальных проектах. Однако при использовании физического движка Arcade возникает проблема: объект, будучи перетаскиваемым, продолжает подчиняться законам физики — отскакивать, падать и сталкиваться, что мешает точному управлению. В этой статье мы разберем элегантное решение из официальных примеров Phaser, которое позволяет временно "отключать" физику для перетаскиваемого тела, обеспечивая полный контроль над его позицией, и возвращать её обратно, когда игрок отпускает объект. Этот подход универсален и применим во множестве игровых сценариев.
Версия Phaser: код и демо в этой статье рассчитаны на Phaser 3.90.0.
Живой запуск
Ниже встроен рабочий билд примера. Оригинальный источник: GitHub.
Исходный код
class Example extends Phaser.Scene
{
block;
preload ()
{
this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.image('block', 'assets/sprites/block.png');
}
create ()
{
this.block = this.physics.add.image(400, 100, 'block')
.setVelocity(100, 200)
.setBounce(1, 1)
.setCollideWorldBounds(true);
this.input.setDraggable(this.block.setInteractive());
this.input.on('dragstart', (pointer, obj) =>
{
obj.body.moves = false;
});
this.input.on('drag', (pointer, obj, dragX, dragY) =>
{
obj.setPosition(dragX, dragY);
});
this.input.on('dragend', (pointer, obj) =>
{
obj.body.moves = true;
});
}
}
const config = {
type: Phaser.AUTO,
width: 800,
height: 600,
parent: 'phaser-example',
physics: {
default: 'arcade',
arcade: {
debug: true,
gravity: { y: 200 }
}
},
scene: Example
};
const game = new Phaser.Game(config);
Настройка сцены и физики
Вся логика примера содержится в классе сцены Example. Первым делом, в методе preload(), загружается спрайт блока.
Ключевой момент происходит в конфигурации игры, где активируется физический движок Arcade с гравитацией и режимом отладки. Это важно: отладка (debug: true) позволяет визуализировать физические тела (их контуры и векторы скорости), что крайне полезно при разработке.
const config = {
type: Phaser.AUTO,
width: 800,
height: 600,
parent: 'phaser-example',
physics: {
default: 'arcade',
arcade: {
debug: true,
gravity: { y: 200 }
}
},
scene: Example
};
Создание и настройка физического тела
В методе create() мы создаем не просто спрайт, а физическое тело с помощью this.physics.add.image. Сразу же задаются его начальные свойства: скорость (setVelocity), упругость (setBounce) и включение столкновений с границами мира (setCollideWorldBounds). Без нашего вмешательства этот блок будет хаотично прыгать по экрану под действием гравитации и отскоков.
create ()
{
this.block = this.physics.add.image(400, 100, 'block')
.setVelocity(100, 200)
.setBounce(1, 1)
.setCollideWorldBounds(true);
Включение перетаскивания
Чтобы объект можно было перетаскивать, его сначала нужно сделать интерактивным с помощью setInteractive(). Затем этот интерактивный объект регистрируется в системе ввода как перетаскиваемый. Обратите внимание на цепочку вызовов: this.block.setInteractive() возвращает тот же объект, который сразу передается в setDraggable().
this.input.setDraggable(this.block.setInteractive());
Логика перетаскивания: отключение и включение физики
Сердце примера — обработка событий ввода: dragstart, drag и dragend. Каждое событие получает в колбэке объект (obj), который мы перетаскиваем (в нашем случае это this.block).
- **dragstart**: В момент начала перетаскивания мы получаем доступ к физическому телу объекта (obj.body) и устанавливаем свойство moves в false. Это свойство движка Arcade отвечает за то, будет ли тело обновляться физическим движком. Отключив его, мы останавливаем влияние гравитации, скорости и столкновений на объект.
- **drag**: Во время самого перетаскивания мы просто обновляем позицию объекта, устанавливая её равной координатам указателя (dragX, dragY). В этот момент тело "заморожено" (moves=false), поэтому мы можем перемещать его абсолютно свободно.
- **dragend**: Когда игрок отпускает объект, мы снова включаем физику (obj.body.moves = true). Тело мгновенно начинает подчиняться законам мира — в нашем случае на него снова начинает действовать гравитация.
this.input.on('dragstart', (pointer, obj) =>
{
obj.body.moves = false;
});
this.input.on('drag', (pointer, obj, dragX, dragY) =>
{
obj.setPosition(dragX, dragY);
});
this.input.on('dragend', (pointer, obj) =>
{
obj.body.moves = true;
});
Что попробовать дальше
Использование свойства body.moves — это простой и эффективный способ временного изъятия физического тела из-под контроля движка для реализации точного перетаскивания. Этот паттерн можно расширять: например, при dragstart можно также обнулять скорость тела (setVelocity(0, 0)), чтобы при отпускании оно не продолжало предыдущее движение. Попробуйте применить этот подход к группе тел или добавить эффекты (например, изменение альфа-канала) при перетаскивании, чтобы улучшить визуальную обратную связь для игрока.
