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

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

Версия 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/gradient29.png');
        this.load.image('char', 'assets/pics/nayuki.png');
    }

    create ()
    {
        this.add.image(400, 300, 'bg');

        this.add.text(16, 16, 'Drag the Sprite').setFontSize(24).setShadow(1, 1);

        const sprite = this.add.sprite(400, 300, 'char');

        sprite.setInteractive({ draggable: true });

        sprite.on('drag', (pointer, dragX, dragY) => sprite.setPosition(dragX, dragY));
        sprite.on('dragend', this.onSceneDragEndHandler);
    }

    onSceneDragEndHandler(pointer, gameObject)
    {
        console.log(pointer.leftButtonReleased(), pointer.rightButtonReleased());
    }
}

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

const game = new Phaser.Game(config);

Суть проблемы: неверное значение при событии dragend

В представленном примере создается простая сцена с перетаскиваемым спрайтом. Когда перетаскивание завершается (dragend), в консоль выводятся результаты вызовов pointer.leftButtonReleased() и pointer.rightButtonReleased().

Ожидается, что после отпускания левой кнопки мыши, метод leftButtonReleased() вернет true. Однако в момент события dragend это не всегда так. Метод может вернуть false, что противоречит интуиции и может сломать логику, зависящую от этого флага.

sprite.on('dragend', this.onSceneDragEndHandler);

// ...
onSceneDragEndHandler(pointer, gameObject)
{
    // В момент вызова этого обработчика значения могут быть неожиданными
    console.log(pointer.leftButtonReleased(), pointer.rightButtonReleased());
}

Причина: разница между "состоянием" и "событием"

Методы leftButtonReleased() и rightButtonReleased() объекта Pointer не отражают факт того, какая кнопка инициировала *текущее* событие. Вместо этого они возвращают **состояние кнопки в данный конкретный кадр**.

Событие dragend генерируется системой Input Phaser'а. К моменту, когда ваш обработчик onSceneDragEndHandler выполняется, состояние мыши уже могло быть обновлено для нового кадра. Если между отпусканием кнопки и вызовом вашего кода прошла хотя бы одна итерация игрового цикла, методы вернут false, так как система уже «забыла», что кнопка только что была отпущена.

Проще говоря, эти методы отвечают на вопрос: «Была ли кнопка отпущена *прямо сейчас*, в *этом* кадре?». А не на вопрос: «Какая кнопка вызвала событие dragend?».

Практическое решение: используйте свойства события

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

Для этого нужно обратиться к свойству pointer.event.button. Это нативное свойство JavaScript-события мыши, которое содержит числовой код отпущенной кнопки: - `0` — левая кнопка (основная). - `1` — средняя кнопка (колесо). - `2` — правая кнопка.

Вот как должен выглядеть исправленный обработчик:

onSceneDragEndHandler(pointer, gameObject)
{
    // Используем нативное свойство события
    const releasedButton = pointer.event.button;
    
    if (releasedButton === 0) {
        console.log('Отпущена левая кнопка мыши');
    } else if (releasedButton === 2) {
        console.log('Отпущена правая кнопка мыши');
    }
    // Код 1 соответствует средней кнопке
}

Этот подход надежен, потому что pointer.event содержит оригинальный объект DOM-события, который точно указывает на кнопку, инициировавшую действие.

Когда использовать leftButtonReleased()?

Методы leftButtonReleased() и подобные идеально подходят для проверки состояния ввода **внутри игрового цикла**, например, в функции update(). Они помогают реагировать на факт отпускания кнопки в текущем кадре, независимо от того, какое событие его вызвало.

Представьте, что вы хотите, чтобы игрок стрелял, только когда отпускает кнопку мыши. Проверку стоит делать в update():

update()
{
    // Проверяем состояние ввода в игровом цикле
    if (this.input.activePointer.leftButtonReleased()) {
        this.fireWeapon();
    }
}

Для событий, привязанных к конкретному действию (клик, перетаскивание), всегда полагайтесь на данные из pointer.event или другие свойства объекта события Phaser.

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

Ключевой вывод — различать **проверку состояния ввода** (leftButtonReleased()) и **анализ произошедшего события** (pointer.event.button). Для реакций на конкретные события, такие как dragend, pointerup или pointerdown, используйте данные из объекта события. Для опроса состояния ввода в любой момент времени — используйте методы вида leftButtonReleased(). Поэкспериментируйте: попробуйте обрабатывать pointerup на всей сцене одновременно с dragend на спрайте и сравните значения pointer.event.button. Или создайте логику, где действие в update() зависит от leftButtonReleased(), а визуальная обратная связь — от данных события.