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

Создание игр с перетаскиваемыми объектами часто требует динамической визуальной обратной связи. В этом примере мы покажем, как изменять масштаб спрайта в реальном времени, пока пользователь его перемещает, основываясь на его положении по оси Y. Этот приём полезен для создания интуитивных интерфейсов, головоломок или инвентаря, где размер объекта может указывать на его приоритет или расстояние до целевой зоны. Мы разберём код по косточкам и объясним механику работы событий `drag` и `drop`.

Версия 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.atlas('cards', 'assets/atlas/cards.png', 'assets/atlas/cards.json');
    }

    create ()
    {
        const height = 400;

        const image = this.add.sprite(100, 300, 'cards').setScale(300 / height).setInteractive();

        this.input.setDraggable(image);

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

            gameObject.setScale(gameObject.y / height);

            gameObject.x = pointer.x;
            gameObject.y = pointer.y;

        });

        this.input.on('drop', (pointer, gameObject, dropZone) =>
        {

            gameObject.x = dropZone.x;
            gameObject.y = dropZone.y;

        });

        //  A drop zone
        const zone = this.add.zone(500, 300, 300, 500).setDropZone();

        //  Just a visual display of the drop zone
        const graphics = this.add.graphics();
        graphics.lineStyle(2, 0xffff00);
        graphics.strokeRect(zone.x + zone.input.hitArea.x, zone.y + zone.input.hitArea.y, zone.input.hitArea.width, zone.input.hitArea.height);

    }
}

const config = {
    type: Phaser.AUTO,
    parent: 'phaser-example',
    width: 800,
    height: 600,
    scene: Example
};

const game = new Phaser.Game(config);

Подготовка сцены и загрузка ассетов

В методе preload мы загружаем атлас с картами. Важно использовать setBaseURL, чтобы указать базовый путь для загрузки, иначе ресурсы не найдутся.

this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.atlas('cards', 'assets/atlas/cards.png', 'assets/atlas/cards.json');

Создание перетаскиваемого спрайта

В методе create мы создаём спрайт карты. Ключевой момент — установка начального масштаба, чтобы спрайт был пропорционален заданной высоте height.

Затем мы делаем спрайт интерактивным с помощью setInteractive() и разрешаем его перетаскивание через this.input.setDraggable(image).

const height = 400;
const image = this.add.sprite(100, 300, 'cards').setScale(300 / height).setInteractive();
this.input.setDraggable(image);

Обработка события перетаскивания

Здесь происходит магия динамического изменения масштаба. Мы подписываемся на событие drag, которое срабатывает при каждом перемещении мыши или касании.

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

Затем мы обновляем координаты спрайта, используя позицию указателя (pointer.x, pointer.y).

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

Создание и визуализация зоны сброса

Чтобы добавить цель для перетаскивания, мы создаём невидимую зону сброса с помощью this.add.zone(). Метод setDropZone() делает её активной для событий drop.

Для наглядности мы рисуем жёлтую рамку по границам хитбокса зоны, используя graphics.strokeRect().

const zone = this.add.zone(500, 300, 300, 500).setDropZone();
const graphics = this.add.graphics();
graphics.lineStyle(2, 0xffff00);
graphics.strokeRect(zone.x + zone.input.hitArea.x, zone.y + zone.input.hitArea.y, zone.input.hitArea.width, zone.input.hitArea.height);

Обработка события сброса

Когда пользователь отпускает спрайт над зоной сброса, срабатывает событие drop. В обработчике мы перемещаем спрайт в центр зоны, фиксируя его новое положение.

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

Что попробовать дальше

Пример демонстрирует, как легко создать интерактивный объект с динамическим масштабированием в Phaser. Для экспериментов попробуйте изменить формулу масштаба: например, замените gameObject.y / height на Math.sin(gameObject.x / 100) для волнообразного эффекта. Также можно добавить плавную интерполяцию масштаба с помощью Tweens или изменить цвет спрайта при перетаскивании.