Drag and drop: dragstart, drag, dragend

В библиотеке Phaser система drag and drop реализована через встроенный Input Plugin, который позволяет делать объекты интерактивными и реагировать на события перетаскивания. Основные события — это dragstart, drag и dragend. Они позволяют отслеживать начало, процесс и завершение перемещения объекта.

Для работы с перетаскиванием необходимо сначала включить интерактивность объекта и активировать для него поддержку drag:

this.sprite.setInteractive({ draggable: true });

Параметр draggable: true указывает Phaser, что объект можно перетаскивать. После этого объект можно подключать к событиям drag.


Событие dragstart

dragstart вызывается один раз в момент начала перетаскивания объекта. Событие передает два основных параметра: сам объект и указатель (pointer), который инициировал перетаскивание.

this.input.on('dragstart', (pointer, gameObject) => {
    gameObject.setTint(0xff0000); // Изменение цвета для визуальной индикации начала перетаскивания
});

Особенности dragstart:

  • Используется для изменения визуального состояния объекта.
  • Позволяет фиксировать начальные координаты для последующего вычисления смещений.
  • Часто применяется для подготовки логики коллизий или проверки зоны отпускания объекта.

Событие drag

drag срабатывает каждый раз, когда объект перемещается. Оно передает три аргумента: указатель, объект и смещение (dragX, dragY).

this.input.on('drag', (pointer, gameObject, dragX, dragY) => {
    gameObject.x = dragX;
    gameObject.y = dragY;
});

Ключевые моменты:

  • Позволяет обновлять позицию объекта в реальном времени.
  • Можно ограничивать движение объекта рамками сцены, вычисляя допустимые координаты:
dragX = Phaser.Math.Clamp(dragX, 0, this.sys.game.config.width);
dragY = Phaser.Math.Clamp(dragY, 0, this.sys.game.config.height);
gameObject.setPosition(dragX, dragY);
  • Может использоваться для создания эффекта “магнитного” притяжения к определенным зонам или объектам.

Событие dragend

dragend срабатывает один раз при отпускании объекта. Передаются указатель и объект, а также сведения о том, был ли drag успешно завершен.

this.input.on('dragend', (pointer, gameObject, dropped) => {
    gameObject.clearTint(); // Снятие визуального выделения
    if (!dropped) {
        gameObject.x = gameObject.input.dragStartX;
        gameObject.y = gameObject.input.dragStartY; // Возврат в начальную позицию
    }
});

Особенности dragend:

  • Позволяет реализовать логику “возврата” объекта на исходную позицию при некорректной попытке броска.
  • Используется для проверки попадания объекта в определенную зону (drop zone) и вызова соответствующей логики.
  • Может быть интегрировано с анимацией завершения движения.

Drop Zones и взаимодействие с drag

Drop zone — это область, куда можно сбрасывать перетаскиваемый объект. Для создания drop zone используется метод this.add.zone(x, y, width, height) и установка интерактивности:

let zone = this.add.zone(400, 300, 200, 150).setRectangleDropZone(200, 150);
zone.setName('targetZone');

Для отслеживания успешного броска объекта в drop zone применяется событие drop:

this.input.on('drop', (pointer, gameObject, dropZone) => {
    gameObject.x = dropZone.x;
    gameObject.y = dropZone.y;
});

Принципы работы:

  • Drop zone не обязательно должна быть видимой. Можно создать невидимую область для логики размещения.
  • Событие dragend полезно для возврата объекта, если он не был брошен в drop zone (dropped = false).
  • Можно комбинировать с эффектами анимации и смены состояний объекта при успешном drop.

Ограничения и оптимизация

  • Множественные объекты: Phaser поддерживает перетаскивание множества объектов одновременно, если каждому включена интерактивность.
  • Производительность: Событие drag вызывается каждый кадр, поэтому тяжелая логика внутри него может снижать FPS. Лучше вычисления ограничивать минимально необходимыми действиями.
  • Зоны ограничения: Для ограничения движения по сцене или сетке используют функции Phaser.Math.Clamp или методы snap-to-grid (gameObject.setSnap).

Примеры продвинутых приемов

  1. Сетка для snap-to-grid:
this.input.on('drag', (pointer, gameObject, dragX, dragY) => {
    const gridSize = 32;
    gameObject.x = Math.round(dragX / gridSize) * gridSize;
    gameObject.y = Math.round(dragY / gridSize) * gridSize;
});
  1. Привязка к объектам:
this.input.on('drag', (pointer, gameObject) => {
    let nearestTarget = this.targets.getChildren().reduce((prev, curr) => {
        let prevDist = Phaser.Math.Distance.Between(gameObject.x, gameObject.y, prev.x, prev.y);
        let currDist = Phaser.Math.Distance.Between(gameObject.x, gameObject.y, curr.x, curr.y);
        return currDist < prevDist ? curr : prev;
    });
    gameObject.x = nearestTarget.x;
    gameObject.y = nearestTarget.y;
});
  1. Анимация при drop:
this.input.on('drop', (pointer, gameObject, dropZone) => {
    this.tweens.add({
        targets: gameObject,
        x: dropZone.x,
        y: dropZone.y,
        duration: 300,
        ease: 'Power2'
    });
});

Эти подходы позволяют создавать интерактивные, отзывчивые интерфейсы и игры, где объекты можно перетаскивать, точно размещать и визуально отслеживать действия пользователя.