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

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

Версия Phaser: код и демо в этой статье рассчитаны на Phaser 3.90.0.

Живой запуск

Ниже встроен рабочий билд примера. Оригинальный источник: GitHub.

Исходный код


class Example extends Phaser.Scene
{
    create ()
    {
        const graphics = this.add.graphics();

        const polygon = new Phaser.Geom.Polygon([
            200, 150,
            400, 300,
            600, 150,
            750, 300,
            600, 450,
            200, 450,
            50, 300
        ]);

        graphics.fillStyle(0x00aa00);
        graphics.fillPoints(polygon.points, true);

        this.input.on('pointermove', pointer =>
        {

            graphics.clear();

            if (Phaser.Geom.Polygon.ContainsPoint(polygon, pointer))
            {
                graphics.fillStyle(0xaa0000);
            }
            else
            {
                graphics.fillStyle(0x00aa00);
            }

            graphics.fillPoints(polygon.points, true);

        });
    }
}

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

const game = new Phaser.Game(config);

Создание полигона и его отрисовка

Вся логика в Phaser размещается внутри методов сцены (Scene). В данном примере работа начинается в методе create. Первым делом мы создаем объект Graphics, который предназначен для программной отрисовки примитивов.

const graphics = this.add.graphics();

Далее мы определяем форму нашего полигона. Класс Phaser.Geom.Polygon принимает в конструктор плоский массив чисел, где каждая пара значений представляет собой координату X и Y вершины многоугольника. Порядок вершин важен — они должны следовать по контуру фигуры.

const polygon = new Phaser.Geom.Polygon([
    200, 150,
    400, 300,
    600, 150,
    750, 300,
    600, 450,
    200, 450,
    50, 300
]);

После создания полигона мы его отрисовываем. Устанавливаем зеленый цвет заливки и вызываем метод fillPoints. Первый аргумент — массив точек полигона (получаем через свойство polygon.points), второй аргумент true указывает, что фигура должна быть замкнута (последняя точка соединится с первой).

graphics.fillStyle(0x00aa00);
graphics.fillPoints(polygon.points, true);

Обработка движения курсора и проверка попадания

Чтобы сделать полигон интерактивным, нужно отслеживать движение мыши. В Phaser для этого используется объект this.input и его событие 'pointermove'.

this.input.on('pointermove', pointer =>
{
    // Код обработчика
});

Каждый раз при движении мыши вызывается функция-обработчик. Ей передается объект pointer, который содержит текущие координаты курсора (pointer.x и pointer.y).

Первое действие внутри обработчика — очистка холста Graphics от предыдущего кадра. Без этого старые отрисованные фигуры будут накладываться на новые.

graphics.clear();

Затем происходит ключевая операция — проверка, находится ли точка курсора внутри полигона. Для этого используется статический метод Phaser.Geom.Polygon.ContainsPoint. Он принимает два аргумента: объект полигона и объект точки (в нашем случае pointer, который также является объектом с координатами). Метод возвращает true или false.

if (Phaser.Geom.Polygon.ContainsPoint(polygon, pointer))
{
    graphics.fillStyle(0xaa0000);
}
else
{
    graphics.fillStyle(0x00aa00);
}

В зависимости от результата проверки мы меняем цвет заливки: красный, если курсор внутри полигона, и зеленый, если снаружи.

Оптимизация и перерисовка

После определения цвета мы заново рисуем полигон с обновленным стилем. Это происходит в каждом кадре, пока движется мышь, создавая эффект динамической подсветки области.

graphics.fillPoints(polygon.points, true);

Важно понимать, что метод ContainsPoint выполняет математический расчет (например, алгоритм "луч") для произвольного полигона. Это эффективно, но для очень сложных фигур с сотнями вершин или при проверке множества точек каждый кадр это может стать узким местом производительности. В таких случаях стоит рассмотреть использование физических тел (Body), предварительную оптимизацию формы или пространственное разбиение.

Конфигурация игры (config) и ее запуск стандартны для Phaser. Обратите внимание, что в конфиге указан наш класс Example в качестве сцены.

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

const game = new Phaser.Game(config);

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

Метод Phaser.Geom.Polygon.ContainsPoint — это мощный и простой инструмент для работы со сложными геометрическими областями в играх. Он снимает необходимость самостоятельно реализовывать нетривиальные алгоритмы проверки вхождения точки в многоугольник. Для экспериментов попробуйте изменить форму полигона, добавить несколько независимых полигонов с разными реакциями или использовать проверку не для курсора, а для позиции игрового спрайта, создав, например, невидимую опасную зону сложной формы.