О чем этот пример
Работа с геометрией и коллизиями — основа для создания сложной игровой механики. В этом примере мы исследуем метод `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 shapes = [ shape1 ];
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);
// console.log(intersects);
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);
});
};
this.input.on('pointerdown', (pointer) => {
const intersects = Phaser.Geom.Intersects.GetRaysFromPointToPolygon(pointer.worldX, pointer.worldY, shapes);
console.log(pointer.worldX, pointer.worldY, intersects);
});
draw({ worldX: 140, worldY: 96 });
// draw({ worldX: 450, worldY: 200 });
// draw({ worldX: 400, worldY: 200 });
this.input.on('pointermove', pointer => draw(pointer));
/*
const line1 = new Phaser.Geom.Line(140, 128, 140, 129);
debug.lineStyle(4, 0xff0000);
debug.strokeLineShape(line1);
const line2 = new Phaser.Geom.Line(140, 210, 100, 150);
debug.lineStyle(1, 0x00ff00);
debug.strokeLineShape(line2);
const c = Phaser.Geom.Intersects.GetLineToLine(line1, line2, true);
console.log('direct test');
console.log(c);
*/
}
}
const config = {
type: Phaser.WEBGL,
width: 800,
height: 600,
backgroundColor: '#2d2d2d',
parent: 'phaser-example',
scene: Example
};
let game = new Phaser.Game(config);
Подготовка сцены и полигонов
В первую очередь в методе create создается граница игрового поля и набор полигонов различных форм. Все они являются экземплярами Phaser.Geom.Polygon. Координаты в массиве определяют вершины каждой фигуры.
Для смещения некоторых полигонов используется статический метод 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);
В демонстрационных целях в массиве shapes оставлен только shape1, чтобы упростить визуализацию и анализ.
Создание луча и графики для отладки
Для визуализации процесса создается луч Phaser.Geom.Line и графический объект debug. Луч изначально является точкой (его начальная и конечная точки совпадают), но в дальнейшем он будет переопределяться.
const ray = new Phaser.Geom.Line(400, 300, 400, 300);
const debug = this.add.graphics();
Графический объект debug будет использоваться для рисования всех элементов: лучей-пересечений, точек пересечения и контуров самих полигонов. Это ключевой инструмент для отладки геометрических вычислений.
Функция отрисовки и метод GetRaysFromPointToPolygon
Основная логика заключена в функции draw. Она принимает объект, похожий на pointer, с координатами worldX и worldY. С помощью метода Phaser.Geom.Intersects.GetRaysFromPointToPolygon вычисляются все точки на границах полигонов из массива shapes, до которых можно провести луч из заданной точки, не пересекая внутреннюю область полигонов.
const intersects = Phaser.Geom.Intersects.GetRaysFromPointToPolygon(pointer.worldX, pointer.worldY, shapes);
Метод возвращает массив объектов Phaser.Geom.Point. Каждая такая точка — это место на границе полигона, куда упирается луч.
Функция draw очищает предыдущую графику, затем для каждой найденной точки пересечения рисует луч от исходной точки до точки на полигоне и закрашивает саму точку пересечения.
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);
});
После этого функция отрисовывает зеленые контуры всех полигонов для наглядности.
Обработка ввода и начальная отрисовка
Для интерактивности к событию движения указателя pointermove привязывается функция draw. Это позволяет в реальном времени наблюдать, как меняются лучи при перемещении курсора.
this.input.on('pointermove', pointer => draw(pointer));
Также добавлен обработчик pointerdown, который выводит в консоль координаты клика и массив найденных пересечений — полезно для детального анализа.
Перед началом интерактивного режима функция draw вызывается один раз с фиксированными координатами, чтобы сразу показать пример работы системы.
draw({ worldX: 140, worldY: 96 });
Что попробовать дальше
Метод GetRaysFromPointToPolygon — мощный инструмент для анализа геометрической видимости. Его можно использовать для создания Field of View (FOV) у стражников в стелс-играх, моделирования лучей света, преломляющихся в препятствиях, или для расчёта зоны поражения кругового заклинания, учитывающего стены. Поэкспериментируйте: добавьте больше полигонов, измените форму луча на сектор (конус) для ограничения угла обзора или попробуйте динамически менять набор shapes в процессе игры.
