О чем этот пример
Работа с коллизиями и наведением мыши на спрайты сложной формы — классическая задача в разработке игр. Стандартный прямоугольный хитбокс часто не подходит, когда объект имеет неправильные очертания. В этой статье мы разберём, как задать полигональную зону взаимодействия для спрайта в 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('car', 'assets/pics/supercars-parsec.png');
}
create ()
{
const sprite = this.add.sprite(400, 300, 'car');
const shape = 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
]);
sprite.setInteractive(shape, Phaser.Geom.Polygon.Contains);
// Input Event listeners
this.input.on('gameobjectover', (pointer, gameObject) =>
{
gameObject.setTint(0x7878ff);
});
this.input.on('gameobjectout', (pointer, gameObject) =>
{
gameObject.clearTint();
});
// Draw the polygon
const graphics = this.add.graphics({ x: sprite.x - sprite.displayOriginX, y: sprite.y - sprite.displayOriginY });
graphics.lineStyle(2, 0x00aa00);
graphics.beginPath();
graphics.moveTo(shape.points[0].x, shape.points[0].y);
for (let i = 1; i < shape.points.length; i++)
{
graphics.lineTo(shape.points[i].x, shape.points[i].y);
}
graphics.closePath();
graphics.strokePath();
}
}
const config = {
type: Phaser.WEBGL,
parent: 'phaser-example',
width: 800,
height: 600,
scene: Example
};
const game = new Phaser.Game(config);
Создание полигональной зоны (hit area)
Вместо того чтобы использовать ограничивающий прямоугольник спрайта (sprite.setInteractive() по умолчанию), мы можем определить произвольную многоугольную область. Для этого используется класс Phaser.Geom.Polygon. Его конструктор принимает массив координат точек [x1, y1, x2, y2, ...], которые задаются относительно точки начала координат (origin) самого спрайта.
const shape = 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
]);
После создания полигона, мы применяем его к спрайту с помощью метода setInteractive. Вторым аргументом передаётся функция проверки принадлежности точки полигону — Phaser.Geom.Polygon.Contains.
sprite.setInteractive(shape, Phaser.Geom.Polygon.Contains);
Теперь наведение и клики будут срабатывать только тогда, когда курсор находится внутри заданного многоугольника.
Обработка событий наведения
После настройки интерактивности можно подписаться на стандартные события ввода Phaser. В примере используются события gameobjectover (курсор вошёл в зону объекта) и gameobjectout (курсор покинул зону).
this.input.on('gameobjectover', (pointer, gameObject) => {
gameObject.setTint(0x7878ff);
});
this.input.on('gameobjectout', (pointer, gameObject) => {
gameObject.clearTint();
});
Обратите внимание, что коллбэк получает два параметра: pointer (объект указателя, содержит координаты и состояние) и gameObject (наш спрайт, на котором произошло событие). Внутри обработчика мы меняем визуальное состояние спрайта — применяем и снимаем цветовой оттенок (tint).
Визуализация полигона для отладки
Во время разработки полезно видеть, как именно задана зона взаимодействия. Для этого можно нарисовать контур полигона поверх спрайта с помощью графического объекта (Graphics). Ключевой момент — корректное позиционирование. Поскольку координаты полигона заданы относительно спрайта, графику нужно сместить в ту же точку.
const graphics = this.add.graphics({
x: sprite.x - sprite.displayOriginX,
y: sprite.y - sprite.displayOriginY
});
Далее стандартными методами графики рисуем ломаную линию по точкам полигона.
graphics.lineStyle(2, 0x00aa00);
graphics.beginPath();
graphics.moveTo(shape.points[0].x, shape.points[0].y);
for (let i = 1; i < shape.points.length; i++)
{
graphics.lineTo(shape.points[i].x, shape.points[i].y);
}
graphics.closePath();
graphics.strokePath();
Этот контур будет точно совпадать с активной зоной наведения, что помогает при настройке формы.
Практические советы по созданию полигона
1. **Определение точек**: Самый простой способ — загрузить изображение спрайта в графическом редакторе, снять координаты ключевых точек по его контуру и скопировать их в массив.
2. **Оптимизация формы**: Не используйте избыточное количество вершин. Чем их меньше, тем быстрее работает проверка Contains. Для плавных кривых часто хватает 10-15 точек.
3. **Относительные координаты**: Помните, что точка (0,0) полигона — это точка начала координат (origin) спрайта. По умолчанию это его центр. Если вы меняли setOrigin, это повлияет на расчёт.
4. **Произвольные формы**: Метод setInteractive также может принимать объект Phaser.Geom.Circle или Phaser.Geom.Ellipse для круглых зон.
Что попробовать дальше
Использование полигональных хитбоксов — мощный приём для повышения точности взаимодействия в играх с объектами сложной формы. Это особенно актуально для point-and-click квестов, интерфейсов с нестандартными кнопками или игр, где важен точный прицел. Для экспериментов попробуйте: создать составной объект из нескольких полигонов, анимировать точки полигона во времени для "дышащей" зоны взаимодействия или генерировать форму хитбокса динамически, основываясь на данных скелетной анимации.
