О чем этот пример
При разработке игр на Phaser управление вводом — критически важная часть. Особенно это касается обработки событий мыши и касаний, которые могут происходить как внутри игрового холста, так и за его пределами. В этой статье мы разберем, как использовать событие `POINTER_UP_OUTSIDE`, чтобы точно отслеживать завершение взаимодействия пользователя, даже если указатель покинул игровую область. Это полезно для реализации корректного поведения перетаскиваемых объектов, кнопок и других интерактивных элементов, предотвращая "залипание" состояний.
Версия 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('block', 'assets/sprites/block.png');
}
create ()
{
this.input.on(Phaser.Input.Events.POINTER_UP_OUTSIDE, () => console.info('phaser pointer up outside'));
this.input.on(Phaser.Input.Events.POINTER_UP, () => console.info('phaser pointer up inside'));
this.add.text(this.scale.width / 2, this.scale.height / 2, 'This is a test Phaser game', { align: 'center', color: '#ffffff', fontSize: '32px', fontFamily: 'Arial' }).setOrigin(0.5);
this.game.canvas.addEventListener('mousedown', () => console.log('mouse down on canvas'));
const block = this.physics.add.image(400, 100, 'block')
.setVelocity(100, 200)
.setBounce(1, 1)
.setCollideWorldBounds(true);
this.input.setDraggable(block.setInteractive());
this.input.on('dragstart', (pointer, obj) =>
{
obj.body.moves = false;
});
this.input.on('drag', (pointer, obj, dragX, dragY) =>
{
obj.setPosition(dragX, dragY);
});
this.input.on('dragend', (pointer, obj) =>
{
obj.body.moves = true;
});
}
}
const config = {
type: Phaser.WEBGL,
parent: 'phaser-example',
width: 800,
height: 600,
physics: {
default: 'arcade',
arcade: {
debug: true,
gravity: { y: 200 }
}
},
scene: Example
};
const game = new Phaser.Game(config);
window.addEventListener('mouseup', () => console.info('mouse up on window'));
События Phaser и нативные события DOM
Phaser предоставляет собственную систему событий для ввода, которая абстрагирует разницу между мышью и касаниями, используя общее понятие pointer. Однако иногда требуется знать, где именно произошло событие: внутри игрового холста или снаружи.
В примере кода наглядно показано это различие. События Phaser, такие как POINTER_UP и POINTER_UP_OUTSIDE, отслеживаются системой ввода движка. Параллельно с этим код добавляет нативное слушатель событий mouseup на объект window. Это позволяет сравнить поведение.
this.input.on(Phaser.Input.Events.POINTER_UP_OUTSIDE, () => console.info('phaser pointer up outside'));
this.input.on(Phaser.Input.Events.POINTER_UP, () => console.info('phaser pointer up inside'));
window.addEventListener('mouseup', () => console.info('mouse up on window'));
При отпускании кнопки мыши внутри холста сработают POINTER_UP и глобальный mouseup. Если отпустить кнопку за пределами холста, сработают POINTER_UP_OUTSIDE и тот же глобальный mouseup. Это ключевая особенность для обработки drag&drop.
Практический пример: перетаскивание физического тела
Рассмотрим, как событие POINTER_UP_OUTSIDE интегрируется в логику перетаскивания спрайта с физикой. В примере создается физический блок (block), который делается перетаскиваемым.
const block = this.physics.add.image(400, 100, 'block')
.setVelocity(100, 200)
.setBounce(1, 1)
.setCollideWorldBounds(true);
this.input.setDraggable(block.setInteractive());
Для управления физикой во время перетаскивания используются события dragstart, drag и dragend.
this.input.on('dragstart', (pointer, obj) =>
{
obj.body.moves = false;
});
this.input.on('drag', (pointer, obj, dragX, dragY) =>
{
obj.setPosition(dragX, dragY);
});
this.input.on('dragend', (pointer, obj) =>
{
obj.body.moves = true;
});
Здесь dragstart отключает движение физического тела (body.moves = false), чтобы его не увлекали силы гравитации или столкновения во время перетаскивания. Событие dragend включает движение обратно. Важно, что dragend в Phaser срабатывает как при отпускании внутри холста, так и при срабатывании POINTER_UP_OUTSIDE. Это гарантирует, что физика тела всегда будет восстановлена, даже если пользователь "выбросил" мышку за пределы игры.
Отладка и анализ потока событий
Чтобы понять последовательность событий, в исходном примере добавлены console.log. Это отличный способ для отладки.
this.game.canvas.addEventListener('mousedown', () => console.log('mouse down on canvas'));
Нативное событие mousedown на холсте поможет точно зафиксировать начало взаимодействия. Сравнивая вывод в консоль, можно построить точную картину:
1. mouse down on canvas – начало перетаскивания.
2. Событие dragstart в Phaser (без лога, но оно отключает физику).
3. Множество событий drag при движении мыши.
4. В зависимости от места отпускания кнопки:
* Внутри холста: phaser pointer up inside, затем dragend, и mouse up on window.
* Снаружи холста: phaser pointer up outside, затем dragend, и mouse up on window.
Такой подход позволяет убедиться, что логика завершения перетаскивания (dragend) отрабатывает в любом случае, предотвращая ситуацию, когда тело навсегда останется в "замороженном" состоянии (body.moves = false).
Конфигурация сцены и физики
Для работы примера необходима правильная настройка игры. Ключевые моменты конфигурации:
const config = {
type: Phaser.WEBGL,
parent: 'phaser-example',
width: 800,
height: 600,
physics: {
default: 'arcade',
arcade: {
debug: true, // Визуализация физических тел для отладки
gravity: { y: 200 }
}
},
scene: Example
};
Установка debug: true в настройках Arcade Physics включает отображение контуров тел и векторов скорости, что невероятно полезно при настройке взаимодействия. Гравитация { y: 200 } заставляет блок падать вниз, наглядно демонстрируя, как свойство body.moves временно отменяет это воздействие во время перетаскивания.
Что попробовать дальше
Событие POINTER_UP_OUTSIDE — это важный инструмент в арсенале разработчика Phaser для создания отзывчивого и надежного пользовательского ввода. Оно гарантирует, что интерактивные элементы, особенно перетаскиваемые, корректно завершают свои действия независимо от действий пользователя. Для экспериментов попробуйте
- Создать UI-кнопку, которая должна "отжиматься" только если указатель отпущен над ней (используйте
POINTER_UP), а не снаружи - Реализовать механику "броска" объекта, где вектор скорости в момент
dragendрассчитывается и применяется к телу, и убедиться, что это работает даже при отпускании за пределами окна
