О чем этот пример
Создание отзывчивых игровых интерфейсов требует точной обработки взаимодействий с множеством элементов. В этой статье разберем, как эффективно управлять событиями нажатия и отпускания для десятков или сотен объектов на сцене. Вы узнаете, как использовать события `gameobjectdown` и `gameobjectup` для создания интерактивных сеток карт, а также как комбинировать их с общими событиями ввода для отладки и визуальной обратной связи.
Версия 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 ()
{
this.createCards2();
this.input.on('pointerdown', function (pointer)
{
console.log(this.game.getFrame(), 'pd', pointer.x, pointer.y);
this.add.rectangle(pointer.x - 4, pointer.y - 4, 8, 8, 0xff00ff);
}, this);
this.input.on('pointerup', function (pointer)
{
this.add.rectangle(pointer.x - 4, pointer.y - 4, 8, 8, 0x00ff00);
}, this);
this.input.on('gameobjectdown', function (pointer, gameObject)
{
if (gameObject.alpha === 1)
{
this.tweens.add({
targets: gameObject,
alpha: 0,
scaleX: 0,
scaleY: 0
});
gameObject.disableInteractive();
}
}, this);
this.input.on('gameobjectup', function (pointer, gameObject)
{
if (gameObject.alpha === 1)
{
this.tweens.add({
targets: gameObject,
alpha: 0,
scaleX: 0,
scaleY: 0
});
gameObject.disableInteractive();
}
}, this);
}
createCards ()
{
const frames = this.textures.get('cards').getFrameNames();
for (let i = 0; i < 64; i++)
{
const x = Phaser.Math.Between(0, 800);
const y = Phaser.Math.Between(0, 600);
const s = Phaser.Math.FloatBetween(0.5, 1);
this.add.image(x, y, 'cards', Phaser.Math.RND.pick(frames)).setScale(s).setInteractive();
}
}
createCards2 ()
{
const frames = this.textures.get('cards').getFrameNames();
for (let y = 0; y < 8; y++)
{
for (let x = 0; x < 15; x++)
{
this.add.image(x * 56, y * 75, 'cards', Phaser.Math.RND.pick(frames)).setScale(0.4).setOrigin(0).setInteractive();
}
}
}
}
const config = {
type: Phaser.WEBGL,
parent: 'phaser-example',
width: 800,
height: 600,
scene: Example
};
const game = new Phaser.Game(config);
Подготовка ассетов и создание сетки объектов
В методе preload загружается атлас текстур с картами. Ключевой метод — createCards2, который создает упорядоченную сетку из 120 карт (15x8). Каждая карта — это интерактивный объект Image.
createCards2 ()
{
const frames = this.textures.get('cards').getFrameNames();
for (let y = 0; y < 8; y++)
{
for (let x = 0; x < 15; x++)
{
this.add.image(x * 56, y * 75, 'cards', Phaser.Math.RND.pick(frames)).setScale(0.4).setOrigin(0).setInteractive();
}
}
}
Циклы for расставляют карты в сетку с фиксированными интервалами (56px по X, 75px по Y). Phaser.Math.RND.pick(frames) случайно выбирает кадр из атласа для каждой карты. Вызов setInteractive() делает каждый объект Image чувствительным к вводу. setOrigin(0) устанавливает точку отсчета (origin) в левый верхний угол, что упрощает позиционирование сетки.
События ввода: общие и объектно-специфичные
Phaser разделяет события ввода на два типа: общие (на сцену) и объектно-специфичные. В примере настраиваются оба.
this.input.on('pointerdown', function (pointer) {
console.log(this.game.getFrame(), 'pd', pointer.x, pointer.y);
this.add.rectangle(pointer.x - 4, pointer.y - 4, 8, 8, 0xff00ff);
}, this);
Событие 'pointerdown' срабатывает при любом клике на канвасе. В обработчике выводится номер кадра и координаты в консоль, а также рисуется маленький розовый квадрат для визуальной отладки позиции клика. Контекст this передается третьим аргументом, чтобы внутри функции оставалась ссылка на сцену.
this.input.on('gameobjectdown', function (pointer, gameObject) {
if (gameObject.alpha === 1) {
this.tweens.add({
targets: gameObject,
alpha: 0,
scaleX: 0,
scaleY: 0
});
gameObject.disableInteractive();
}
}, this);
Событие 'gameobjectdown' срабатывает только при клике на объект, у которого вызван setInteractive(). В обработчик передаются указатель pointer и сам объект gameObject. Если объект еще видим (alpha = 1), запускается твин, который одновременно скрывает и уменьшает его, а затем disableInteractive() отключает дальнейшие взаимодействия с этим объектом. Аналогичная логика реализована для события 'gameobjectup'.
Практическое применение и логика взаимодействия
Комбинация событий позволяет создавать сложное поведение. Например, 'pointerdown' и 'pointerup' можно использовать для системных действий или визуальных маркеров (зеленый квадрат при отпускании), в то время как 'gameobjectdown' и 'gameobjectup' отвечают за логику конкретных игровых элементов.
if (gameObject.alpha === 1) {
this.tweens.add({
targets: gameObject,
alpha: 0,
scaleX: 0,
scaleY: 0
});
gameObject.disableInteractive();
}
Проверка alpha === 1 предотвращает повторную обработку клика на уже исчезающем объекте. Твин использует несколько свойств одновременно, создавая плавный эффект "схлопывания". После начала анимации объект сразу становится неинтерактивным, что предотвращает возможные конфликты ввода. Важно: события 'gameobjectdown' и 'gameobjectup' срабатывают независимо, поэтому в данном примере объект будет исчезать как при нажатии, так и при отпускании кнопки мыши на нем.
Отладка и визуализация событий ввода
Визуализация точек ввода крайне полезна при отладке, особенно когда на сцене много мелких объектов.
this.add.rectangle(pointer.x - 4, pointer.y - 4, 8, 8, 0xff00ff);
Этот код создает розовый прямоугольник в точке нажатия. Смещение на -4 пикселя центрирует квадрат относительно курсора. Аналогичный зеленый квадрат создается при событии 'pointerup'. Эти маркеры помогают увидеть, где именно произошло событие и как оно соотносится с позициями интерактивных объектов.
console.log(this.game.getFrame(), 'pd', pointer.x, pointer.y);
Логирование номера кадра (this.game.getFrame()) позволяет анализировать временные аспекты ввода, например, задержки между событиями. В реальном проекте такую отладочную визуализацию можно включать флагом в конфигурации.
Что попробовать дальше
Обработка множественных кликов через события gameobjectdown и gameobjectup — мощный механизм Phaser для создания сложных интерактивных сцен. Для экспериментов попробуйте: изменить логику, чтобы объекты исчезали только при 'gameobjectup'; добавить разные реакции на левый и правый клик (проверяя pointer.button в обработчике); реализовать перетаскивание объектов, комбинируя события с pointermove; или создать систему выделения, где клик меняет tint объекта вместо его удаления. Эти техники станут основой для UI, инвентарей или игровых полей.
