О чем этот пример
Точное определение области для кликов и перетаскивания — ключевой элемент геймдизайна. По умолчанию Phaser использует для интерактивности прямоугольник, совпадающий с размерами спрайта, что часто приводит к неточному или неинтуитивному поведению. В этой статье мы разберем, как задавать сложные геометрические формы для зоны взаимодействия и, что особенно полезно при отладке, визуализировать эти зоны на экране с помощью встроенных инструментов Phaser.
Версия 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('ball', 'assets/sprites/wizball.png');
this.load.image('chick', 'assets/sprites/budbrain_chick.png');
this.load.image('eye', 'assets/pics/lance-overdose-loader-eye.png');
this.load.image('ship', 'assets/sprites/thrust_ship2.png');
this.load.image('car', 'assets/pics/supercars-parsec.png');
}
create ()
{
const sprite1 = this.add.sprite(200, 550, 'car').setOrigin(0);
sprite1.setInteractive(new Phaser.Geom.Polygon([ 0, 143, 0, 92, 110, 40, 244, 4, 330, 0, 458, 12, 574, 18, 600, 79, 594, 153, 332, 152, 107, 157 ]), Phaser.Geom.Polygon.Contains);
this.input.setDraggable(sprite1);
const sprite2 = this.add.sprite(150, 150, 'ball').setScale(2);
sprite2.setInteractive(new Phaser.Geom.Circle(45, 46, 45), Phaser.Geom.Circle.Contains);
const sprite3 = this.add.sprite(600, 200, 'chick').setScale(2);
sprite3.setInteractive(new Phaser.Geom.Ellipse(33, 65, 66, 133), Phaser.Geom.Ellipse.Contains);
const sprite4 = this.add.sprite(350, 300, 'eye');
sprite4.setInteractive(new Phaser.Geom.Rectangle(0, 0, 128, 128), Phaser.Geom.Rectangle.Contains);
const sprite5 = this.add.sprite(850, 350, 'ship').setScale(8);
sprite5.setInteractive(new Phaser.Geom.Triangle.BuildEquilateral(sprite5.displayOriginX, 0, 32), Phaser.Geom.Triangle.Contains);
// Specify a different debug outline color
this.input.enableDebug(sprite1, 0xff00ff);
this.input.enableDebug(sprite2, 0xffff00);
this.input.enableDebug(sprite3);
this.input.enableDebug(sprite4);
this.input.enableDebug(sprite5);
this.tweens.add({
targets: sprite3,
angle: 360,
repeat: -1,
duration: 6000
});
this.tweens.add({
targets: sprite4,
scale: 2,
yoyo: true,
repeat: -1,
duration: 4000
});
// Input Event listeners
this.input.on('gameobjectover', (pointer, gameObject) =>
{
gameObject.setTint(0x7878ff);
});
this.input.on('gameobjectout', (pointer, gameObject) =>
{
gameObject.clearTint();
});
sprite1.on('drag', (pointer, dragX, dragY) =>
{
sprite1.x = dragX;
sprite1.y = dragY;
});
sprite2.on('pointerdown', () => {
sprite2.visible = !sprite2.visible;
});
}
}
const config = {
type: Phaser.WEBGL,
parent: 'phaser-example',
pixelArt: true,
backgroundColor: '#00a99d',
scene: Example
};
const game = new Phaser.Game(config);
Проблема прямоугольного хитбокса
Когда вы делаете спрайт интерактивным с помощью setInteractive() без параметров, Phaser автоматически создает для него хитбокс в виде прямоугольника (bounding box), который охватывает все пиксели изображения. Для круглого мяча или космического корабля с прозрачными областями это приводит к нежелательному эффекту: игрок может кликнуть «мимо» видимой части спрайта, но все равно активировать событие.
Визуализация помогает сразу увидеть эту проблему и убедиться, что созданная вами геометрическая форма точно соответствует задумке.
Задаем произвольную геометрию хитбокса
Метод setInteractive() может принимать два параметра: объект фигуры из Phaser.Geom и функцию проверки попадания точки в эту фигуру (обычно Contains). Это позволяет точно ограничить зону взаимодействия.
В примере создается пять спрайтов с разными формами хитбоксов:
// Полигональная форма для машины
sprite1.setInteractive(new Phaser.Geom.Polygon([0, 143, 0, 92, 110, 40, 244, 4, 330, 0, 458, 12, 574, 18, 600, 79, 594, 153, 332, 152, 107, 157]), Phaser.Geom.Polygon.Contains);
// Круг для мяча (задан центр и радиус)
sprite2.setInteractive(new Phaser.Geom.Circle(45, 46, 45), Phaser.Geom.Circle.Contains);
// Эллипс для цыпленка (x, y, width, height)
sprite3.setInteractive(new Phaser.Geom.Ellipse(33, 65, 66, 133), Phaser.Geom.Ellipse.Contains);
// Прямоугольник для глаза (x, y, width, height)
sprite4.setInteractive(new Phaser.Geom.Rectangle(0, 0, 128, 128), Phaser.Geom.Rectangle.Contains);
// Равносторонний треугольник для корабля (x, y, size)
sprite5.setInteractive(new Phaser.Geom.Triangle.BuildEquilateral(sprite5.displayOriginX, 0, 32), Phaser.Geom.Triangle.Contains);
Обратите внимание: координаты фигуры задаются относительно **начала координат спрайта** (origin). Если origin не равен (0,0), это нужно учитывать.
Визуализация для отладки: enableDebug
Ключевой метод для отладки — this.input.enableDebug(). Он рисует поверх игрового объекта контур его активной хит-области.
// Включение отладки с кастомным цветом (шестнадцатеричный код)
this.input.enableDebug(sprite1, 0xff00ff); // Пурпурный
this.input.enableDebug(sprite2, 0xffff00); // Желтый
// Включение отладки с цветом по умолчанию (зеленый)
this.input.enableDebug(sprite3);
Этот контур виден только во время разработки и не мешает игровому процессу. Он позволяет одним взглядом проверить, правильно ли вы задали полигон для сложного спрайта или круг для шарообразного объекта.
Обработка событий и интерактивность
После настройки хитбоксов можно назначать обработчики событий. В примере показано несколько типичных сценариев.
Глобальные события gameobjectover и gameobjectout срабатывают при наведении и уводе курсора с любого интерактивного объекта. В примере они применяют и снимают tint (оттенок).
this.input.on('gameobjectover', (pointer, gameObject) => {
gameObject.setTint(0x7878ff);
});
Для конкретного объекта можно назначить уникальные события. Машина (sprite1) стала перетаскиваемой, а по клику на мяч (sprite2) он скрывается или появляется.
// Делаем спрайт перетаскиваемым
this.input.setDraggable(sprite1);
// Обработка перетаскивания: обновляем координаты спрайта
sprite1.on('drag', (pointer, dragX, dragY) => {
sprite1.x = dragX;
sprite1.y = dragY;
});
// По клику переключаем видимость мяча
sprite2.on('pointerdown', () => {
sprite2.visible = !sprite2.visible;
});
Что попробовать дальше
Использование setInteractive() с геометрическими фигурами и enableDebug() для визуализации — мощный дуэт для отладки взаимодействий в Phaser. Это избавляет от догадок и делает процесс точным. Для экспериментов попробуйте: изменить координаты полигона у машины в реальном времени, использовать Phaser.Geom.Triangle.BuildRight для создания корабля другой формы или привязать отладочный контур к камере, чтобы он оставался на экране при ее скролле.
