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

В процессе разработки игр часто возникает задача определения видимости, столкновений лучей или построения векторного поля. Классический подход с перебором всех объектов сцены может быть неэффективен. Пример демонстрирует, как с помощью метода `Phaser.Geom.Intersects.GetRaysFromPointToPolygon` можно мгновенно получить все лучи от точки до контуров набора полигонов. Это мощный инструмент для создания систем освещения, ИИ для врагов, проверки прямой видимости или даже для нестандартных физических взаимодействий.

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

Живой запуск

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

Исходный код


class Example extends Phaser.Scene
{
    constructor ()
    {
        super();
    }

    create ()
    {
        const border = new Phaser.Geom.Polygon([ 0, 0, 800, 0, 800, 600, 0, 600, 0, 0 ]);

        const shape1 = new Phaser.Geom.Polygon([ 100, 150, 120, 50, 200, 80, 140, 210, 100, 150 ]);
        const shape2 = new Phaser.Geom.Polygon([ 100, 200, 120, 250, 60, 300, 100, 200 ]);
        const shape3 = new Phaser.Geom.Polygon([ 200, 260, 220, 150, 300, 200, 350, 320, 200, 260 ]);
        const shape4 = new Phaser.Geom.Polygon([ 340, 60, 360, 40, 370, 70, 340, 60 ]);
        const shape5 = new Phaser.Geom.Polygon([ 450, 190, 560, 170, 540, 270, 430, 290, 450, 190 ]);
        const shape6 = new Phaser.Geom.Polygon([ 400, 95, 580, 50, 480, 150, 400, 95 ]);
        const shape7 = new Phaser.Geom.Polygon([ 100, 150, 120, 50, 200, 80, 140, 210, 100, 150 ]);
        const shape8 = new Phaser.Geom.Polygon([ 100, 200, 120, 250, 60, 300, 100, 200 ]);
        const shape9 = new Phaser.Geom.Polygon([ 200, 260, 220, 150, 300, 200, 350, 320, 200, 260 ]);
        const shape10 = new Phaser.Geom.Polygon([ 340, 60, 360, 40, 370, 70, 340, 60 ]);
        const shape11 = new Phaser.Geom.Polygon([ 450, 190, 560, 170, 540, 270, 430, 290, 450, 190 ]);
        const shape12 = new Phaser.Geom.Polygon([ 400, 95, 580, 50, 480, 150, 400, 95 ]);

        Phaser.Geom.Polygon.Translate(shape2, 0, 150);
        Phaser.Geom.Polygon.Translate(shape5, 50, 0);
        Phaser.Geom.Polygon.Translate(shape7, 550, 200);
        Phaser.Geom.Polygon.Translate(shape8, 300, 200);
        Phaser.Geom.Polygon.Translate(shape9, 280, 170);
        Phaser.Geom.Polygon.Translate(shape10, 140, 480);
        Phaser.Geom.Polygon.Translate(shape11, -300, 270);
        Phaser.Geom.Polygon.Translate(shape12, 200, -30);

        const shapes = [ border, shape1, shape2, shape3, shape4, shape5, shape6, shape7, shape8, shape9, shape10, shape11, shape12 ];

        const ray = new Phaser.Geom.Line(400, 300, 400, 300);

        const debug = this.add.graphics();

        const draw = (pointer) => {

            const intersects = Phaser.Geom.Intersects.GetRaysFromPointToPolygon(pointer.worldX, pointer.worldY, shapes);

            debug.clear();

            //  Draw the intersections

            debug.lineStyle(1, 0xff0000);
            debug.fillStyle(0xff0000);

            intersects.forEach(line => {

                ray.setTo(pointer.worldX, pointer.worldY, line.x, line.y);

                debug.strokeLineShape(ray);
                debug.fillCircle(line.x, line.y, 4);

            });

            //  Draw all shapes

            debug.lineStyle(1, 0x00ff00);

            shapes.forEach(shape => {

                debug.strokePoints(shape.points);

            });

        };

        draw({ worldX: 450, worldY: 200 });

        this.input.on('pointermove', pointer => draw(pointer));

    }
}

const config = {
    type: Phaser.WEBGL,
    width: 800,
    height: 600,
    backgroundColor: '#2d2d2d',
    parent: 'phaser-example',
    scene: Example
};

let game = new Phaser.Game(config);

Суть метода GetRaysFromPointToPolygon

Метод Phaser.Geom.Intersects.GetRaysFromPointToPolygon решает конкретную геометрическую задачу. Для заданной точки и массива полигонов он находит кратчайшие отрезки (лучи) от этой точки до каждого ребра каждого полигона, которое 'видно' из этой точки. Под 'видимым' ребром подразумевается такое ребро, для которого из точки можно провести перпендикуляр, падающий именно на отрезок ребра, а не на его продолжение.

Это не просто поиск пересечений бесконечных лучей, а вычисление именно минимальных расстояний до границ фигур. Метод возвращает массив точек (Phaser.Geom.Point), каждая из которых является конечной точкой такого луча (точкой на ребре полигона).

Подготовка данных: полигоны и их трансформация

В примере создается набор из 13 полигонов. Первый полигон border представляет собой границу сцены (прямоугольник). Остальные 12 — это различные произвольные фигуры, заданные массивами координат вершин. Важно, что полигон должен быть замкнутым: последняя точка в массиве должна совпадать с первой.

Для разнообразия расположения некоторые полигоны сдвигаются с помощью статического метода Phaser.Geom.Polygon.Translate. Этот метод модифицирует исходный полигон, добавляя смещение по осям X и Y ко всем его вершинам.

const border = new Phaser.Geom.Polygon([0, 0, 800, 0, 800, 600, 0, 600, 0, 0]);
const shape1 = new Phaser.Geom.Polygon([100, 150, 120, 50, 200, 80, 140, 210, 100, 150]);

Phaser.Geom.Polygon.Translate(shape2, 0, 150); // Сдвигаем shape2 на 150 пикселей вниз

Все полигоны собираются в единый массив shapes, который и будет передан в метод расчета лучей.

Инициализация луча и отладочной графики

Для визуализации создается луч Phaser.Geom.Line. Изначально он вырожден (начальная и конечная точки совпадают), но в функции отрисовки его конец будет обновляться.

Ключевой объект для отладки — this.add.graphics(). Он позволяет рисовать примитивы (линии, точки, фигуры) напрямую на сцене, что идеально подходит для визуализации геометрических расчетов.

const ray = new Phaser.Geom.Line(400, 300, 400, 300);
const debug = this.add.graphics();

Функция отрисовки и обработка ввода

Вся логика визуализации инкапсулирована в функции draw. Она принимает объект, похожий на pointer, с координатами worldX и worldY.

1. **Расчет:** Вызывается GetRaysFromPointToPolygon с текущими координатами указателя и массивом полигонов. Возвращается массив точек пересечения. 2. **Очистка:** Графический контекст debug очищается вызовом clear(). 3. **Отрисовка лучей:** Для каждой полученной точки создается луч от курсора до этой точки и рисуется красной линией. Сама точка отмечается красным кружком. 4. **Отрисовка полигонов:** Все полигоны из массива shapes обводятся зелеными линиями.

const draw = (pointer) => {
    const intersects = Phaser.Geom.Intersects.GetRaysFromPointToPolygon(pointer.worldX, pointer.worldY, shapes);
    debug.clear();
    debug.lineStyle(1, 0xff0000);
    debug.fillStyle(0xff0000);
    intersects.forEach(line => {
        ray.setTo(pointer.worldX, pointer.worldY, line.x, line.y);
        debug.strokeLineShape(ray);
        debug.fillCircle(line.x, line.y, 4);
    });
    debug.lineStyle(1, 0x00ff00);
    shapes.forEach(shape => {
        debug.strokePoints(shape.points);
    });
};

Функция вызывается один раз при старте с фиксированными координатами, а затем на каждое движение мыши (pointermove).

Конфигурация игры и запуск

Создается стандартная конфигурация игры Phaser. Ключевые параметры: - type: Phaser.WEBGL для использования аппаратного ускорения. - width и height задают размеры сцены, совпадающие с граничным полигоном. - В scene передается класс нашей сцены Example.

const config = {
    type: Phaser.WEBGL,
    width: 800,
    height: 600,
    backgroundColor: '#2d2d2d',
    parent: 'phaser-example',
    scene: Example
};
let game = new Phaser.Game(config);

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

Метод GetRaysFromPointToPolygon — это эффективный способ получить геометрическую информацию о ближайших границах сложного набора препятствий. На его основе можно строить системы 2D-освещения с тенями от полигональных объектов, реализовывать ИИ стража, который 'видит' игрока только если нет препятствий, или рассчитывать зоны поражения для оружия веерного типа. Для экспериментов попробуйте изменить форму полигонов, добавить динамические объекты в массив shapes или использовать полученные лучи для расчета силы отталкивания от стен в векторном поле.