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

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

Версия Phaser: код и демо в этой статье рассчитаны на Phaser 3.90.0.

Живой запуск

Ниже встроен рабочий билд примера. Оригинальный источник: GitHub.

Исходный код


class Example extends Phaser.Scene
{
    constructor()
    {
        super();
    }

    preload ()
    {
        this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
        this.load.image('bg', 'assets/skies/cavern1.png');
        this.load.image('goblin', 'assets/pics/goblin.png');
        this.load.image('spider', 'assets/pics/spider.png');
    }

    create ()
    {
        this.add.image(400, 300, 'bg');
        this.add.text(16, 16, 'Move 32px before drag starts').setFontSize(24).setShadow(1, 1);

        this.add.image(200, 300, 'goblin').setInteractive({ draggable: true });
        this.add.image(600, 300, 'spider').setInteractive({ draggable: true });

        //  The pointer has to move 32 pixels before it's considered as a drag
        this.input.dragDistanceThreshold = 32;

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

            gameObject.setPosition(dragX, dragY);

        });
    }
}

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

const game = new Phaser.Game(config);

Что такое dragDistanceThreshold?

Свойство this.input.dragDistanceThreshold определяет минимальное расстояние в пикселях, которое должен пройти указатель (мышь или палец) после нажатия, прежде чем событие будет считаться началом перетаскивания (dragstart).

Пока перемещение меньше этого порога, объект не начнет двигаться. Это позволяет четко разделить действия "клик/тап" и "перетаскивание". Значение по умолчанию равно 0, что означает мгновенное начало драга при любом движении.

В нашем примере порог установлен в 32 пикселя. Это значит, что игрок должен явно "потянуть" гоблина или паука, прежде чем они начнут следовать за курсором.

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

Первым делом в методе preload загружаются необходимые изображения: фон и два спрайта для перетаскивания.

preload ()
{
    this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
    this.load.image('bg', 'assets/skies/cavern1.png');
    this.load.image('goblin', 'assets/pics/goblin.png');
    this.load.image('spider', 'assets/pics/spider.png');
}

В методе create мы добавляем фон и поясняющий текст. Затем создаем два интерактивных спрайта — гоблина и паука. Ключевой момент — вызов метода .setInteractive({ draggable: true }) для каждого из них. Параметр draggable: true автоматически включает для объекта базовую поддержку перетаскивания.

create ()
{
    this.add.image(400, 300, 'bg');
    this.add.text(16, 16, 'Move 32px before drag starts').setFontSize(24).setShadow(1, 1);

    this.add.image(200, 300, 'goblin').setInteractive({ draggable: true });
    this.add.image(600, 300, 'spider').setInteractive({ draggable: true });
    // ... настройка порога и обработчика
}

Установка порога и обработка события drag

Сердце примера — строка, устанавливающая порог. Мы обращаемся к менеджеру ввода сцены (this.input) и задаем нужное значение его свойству dragDistanceThreshold.

//  The pointer has to move 32 pixels before it's considered as a drag
this.input.dragDistanceThreshold = 32;

Это глобальная настройка для всей сцены. После ее установки все объекты с draggable: true будут подчиняться этому правилу.

Далее мы настраиваем обработчик для события 'drag'. Это событие генерируется непрерывно во время перемещения указателя с зажатой кнопкой, но только после того, как преодолен порог dragDistanceThreshold. В обработчике мы получаем текущие координаты перетаскивания (dragX, dragY) и применяем их к перетаскиваемому игровому объекту (gameObject).

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

Таким образом, логика перемещения объекта отделена от логики определения начала перетаскивания. Phaser сам решает, когда начать отправлять события drag, основываясь на заданном пороге.

Практические сценарии использования

Настройка dragDistanceThreshold полезна в различных жанрах: 1. **Казуальные и головоломки-перетаскивания**: Устраняет случайное смещение объекта при попытке его "тапнуть". 2. **Карточные игры**: Позволяет игроку рассмотреть карту, слегка сдвинув ее, не начиная немедленного розыгрыша. 3. **Стратегии и RPG**: Четкое разделение между выбором юнита (клик) и отдачей команды на перемещение (перетаскивание). 4. **Интерфейсы редакторов уровней**: Повышает точность при работе с мелкими элементами.

Вы можете динамически менять это значение. Например, увеличить порог для мелких объектов на уровне или уменьшить для крупных.

// Пример динамического изменения
this.smallObject.on('pointerdown', () => {
    this.input.dragDistanceThreshold = 16;
});
this.largeObject.on('pointerdown', () => {
    this.input.dragDistanceThreshold = 64;
});

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

Свойство dragDistanceThreshold — это простая, но крайне эффективная настройка для тонкого контроля над механикой перетаскивания. Оно значительно улучшает пользовательский опыт, делая взаимодействие с игрой предсказуемым и комфортным. **Идеи для экспериментов:** 1. Свяжите значение порога с размером или типом объекта. 2. Попробуйте установить очень большое значение (например, 100px) и посмотрите, как это ощущается в управлении. 3. Обработайте событие dragstart, чтобы добавить визуальный эффект (например, увеличение тени), сигнализирующий о том, что порог преодолен и драг начался. 4. Используйте разные пороги для сенсорного ввода (this.input.touch) и мыши (this.input.mouse), если это необходимо.