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

В Phaser 3 свойство `scrollFactor` позволяет привязать движение объекта к перемещению камеры, создавая эффект параллакса. Но что происходит, когда такие объекты нужно перетаскивать мышью? Этот пример наглядно демонстрирует механику и тонкости интерактивного взаимодействия с объектами, у которых разная скорость прокрутки. Вы научитесь создавать перетаскиваемые элементы в многослойной сцене, правильно обрабатывать события и управлять визуальными состояниями.

Версия 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('eye', 'assets/pics/lance-overdose-loader-eye.png');
    }

    create ()
    {
        const image1 = this.add.sprite(200, 300, 'eye').setInteractive();
        const image2 = this.add.sprite(400, 300, 'eye').setInteractive();
        const image3 = this.add.sprite(600, 300, 'eye').setInteractive();

        this.input.setDraggable([ image1, image2, image3 ]);

        image1.setScrollFactor(1);
        image2.setScrollFactor(0.7);
        image3.setScrollFactor(0.5);

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

            gameObject.setTint(0x00ff00);

        });

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

            gameObject.clearTint();

        });

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

            gameObject.setTint(0xff0000);

        });

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

            gameObject.x = dragX;
            gameObject.y = dragY;

        });

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

            gameObject.clearTint();

        });
    }
}

const config = {
    type: Phaser.WEBGL,
    parent: 'phaser-example',
    scene: Example
};

const game = new Phaser.Game(config);

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

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

В методе create создаются три спрайта на одной горизонтальной линии. Ключевой момент — каждый из них сразу помечается как интерактивный с помощью метода .setInteractive(). Без этого объекты не смогут реагировать на события ввода (мышь, касание).

const image1 = this.add.sprite(200, 300, 'eye').setInteractive();
const image2 = this.add.sprite(400, 300, 'eye').setInteractive();
const image3 = this.add.sprite(600, 300, 'eye').setInteractive();

Далее все три спрайта добавляются в список перетаскиваемых объектов глобального менеджера ввода this.input. Это обязательный шаг для активации событий drag.

this.input.setDraggable([ image1, image2, image3 ]);

Настройка Scroll Factor и его влияние

Scroll Factor определяет, насколько быстро объект будет двигаться относительно камеры. Значение 1.0 означает, что объект движется с той же скоростью, что и камера (стандартный слой). Значения меньше единицы создают эффект фонового слоя, который движется медленнее.

image1.setScrollFactor(1);
image2.setScrollFactor(0.7);
image3.setScrollFactor(0.5);

Объект image3 с фактором 0.5 будет казаться самым дальним. Важно понимать, что scrollFactor влияет на отрисовку позиции объекта при движении камеры, но не меняет его реальные координаты в мире игры. Это становится критичным при перетаскивании, так как мы работаем именно с мировыми координатами gameObject.x и gameObject.y.

Обработка событий наведения и ухода курсора

Для улучшения пользовательского опыта объекты визуально реагируют на наведение курсора. Событие gameobjectover срабатывает, когда курсор оказывается над интерактивным объектом. В обработчике объект окрашивается в зеленый цвет с помощью setTint.

this.input.on('gameobjectover', (pointer, gameObject) => {
    gameObject.setTint(0x00ff00);
});

Событие gameobjectout срабатывает при уходе курсора с объекта. Обработчик снимает tint, возвращая спрайту исходный вид.

this.input.on('gameobjectout', (pointer, gameObject) => {
    gameObject.clearTint();
});

Эти события работают независимо от того, перетаскивается объект или нет.

Логика перетаскивания: начало, процесс и завершение

Механика перетаскивания разделена на три этапа, каждый из которых обрабатывается своим событием.

1. **dragstart**: Срабатывает в момент начала перетаскивания (кнопка мыши зажата над объектом). Объект окрашивается в красный цвет.

this.input.on('dragstart', (pointer, gameObject) => {
        gameObject.setTint(0xff0000);
    });

2. **drag**: Срабатывает непрерывно при перемещении мыши с зажатой кнопкой. В колбэк передаются целевые координаты dragX и dragY. Именно в них нужно установить позицию объекта, чтобы он следовал за курсором.

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

3. **dragend**: Срабатывает при отпускании кнопки мыши. С объекта снимается красный tint. Зеленый tint при наведении будет применен заново, если курсор остался над объектом.

this.input.on('dragend', (pointer, gameObject) => {
        gameObject.clearTint();
    });

Координаты dragX/dragY уже являются мировыми координатами, что позволяет корректно перемещать объекты с разным scrollFactor.

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

Пример демонстрирует целостную систему перетаскивания в Phaser 3, которая корректно работает с многослойной графикой. Основной вывод: scrollFactor влияет только на визуальное отображение относительно камеры, а логика перетаскивания оперирует абсолютными мировыми координатами, что обеспечивает предсказуемое поведение. Для экспериментов попробуйте добавить движение камеры во время перетаскивания, изменить scrollFactor динамически или реализовать 'привязку' объекта к определенным слоям при отпускании.