О чем этот пример

Механика перетаскивания объектов — частый гость в играх-головоломках, конструкторах и казуальных проектах. Однако при использовании физического движка 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)), чтобы при отпускании оно не продолжало предыдущее движение. Попробуйте применить этот подход к группе тел или добавить эффекты (например, изменение альфа-канала) при перетаскивании, чтобы улучшить визуальную обратную связь для игрока.