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

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

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

Живой запуск

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

Исходный код


class Example extends Phaser.Scene
{
    text;
    reflectedLine;
    reflectingLine;
    aimLine;
    graphics;

    create ()
    {
        this.graphics = this.add.graphics({ lineStyle: { width: 4, color: 0xaa00aa } });

        this.aimLine = new Phaser.Geom.Line(100, 100, 400, 300);
        this.reflectingLine = new Phaser.Geom.Line(250, 300, 550, 300);
        this.reflectedLine = new Phaser.Geom.Line(250, 300, 550, 300);

        this.text = this.add.text(50, 50, '');

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

            this.aimLine.x1 = pointer.x;
            this.aimLine.y1 = pointer.y;

        });
    }

    update ()
    {
        Phaser.Geom.Line.Rotate(this.reflectingLine, 0.005);

        const reflectAngle = Phaser.Geom.Line.ReflectAngle(this.aimLine, this.reflectingLine);

        this.text.setText(`Reflect Angle: ${Phaser.Math.RadToDeg(reflectAngle)}`);

        this.graphics.clear();

        this.graphics.strokeLineShape(this.aimLine);

        this.graphics.lineStyle(4, 0x0000aa);// blue
        this.graphics.strokeLineShape(this.reflectingLine);

        const length = Phaser.Geom.Line.Length(this.aimLine);

        Phaser.Geom.Line.SetToAngle(this.reflectedLine, 400, 300, reflectAngle, length);

        this.graphics.lineStyle(2, 0x00aa00);// green
        this.graphics.strokeLineShape(this.reflectedLine);
    }
}

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

const game = new Phaser.Game(config);

Инициализация сцены и объектов

Класс Example расширяет Phaser.Scene. В нем объявляются несколько переменных для хранения графики и геометрических объектов.

В методе create() происходит начальная настройка: 1. Создается объект Graphics (this.graphics) для рисования линий на сцене. Изначально задается стиль линии. 2. Создаются три объекта линии (Phaser.Geom.Line): * aimLine — луч, который будет падать на отражающую поверхность. Его начальная точка будет следовать за курсором. * reflectingLine — сама отражающая поверхность (линия). * reflectedLine — луч, который мы будем вычислять и рисовать как результат отражения. 3. Добавляется текстовый объект для вывода числового значения угла. 4. На событие перемещения указателя (pointermove) вешается обработчик, который обновляет координаты начала aimLine, заставляя его следовать за курсором мыши.

class Example extends Phaser.Scene
{
    text;
    reflectedLine;
    reflectingLine;
    aimLine;
    graphics;

    create ()
    {
        this.graphics = this.add.graphics({ lineStyle: { width: 4, color: 0xaa00aa } });

        this.aimLine = new Phaser.Geom.Line(100, 100, 400, 300);
        this.reflectingLine = new Phaser.Geom.Line(250, 300, 550, 300);
        this.reflectedLine = new Phaser.Geom.Line(250, 300, 550, 300);

        this.text = this.add.text(50, 50, '');

        this.input.on('pointermove', pointer =>
        {
            this.aimLine.x1 = pointer.x;
            this.aimLine.y1 = pointer.y;
        });
    }

Магия расчета в update()

Каждый кадр в методе update() происходят ключевые вычисления и отрисовка.

1. **Вращение отражателя:** Отражающая линия (reflectingLine) медленно вращается для наглядности.

Phaser.Geom.Line.Rotate(this.reflectingLine, 0.005);

2. **Расчет угла отражения:** Это самая важная часть. Метод Phaser.Geom.Line.ReflectAngle принимает на вход две линии: падающий луч (aimLine) и отражающую поверхность (reflectingLine). Он возвращает угол (в радианах), под которым должен двигаться отраженный луч. Вся сложная тригонометрия скрыта внутри этого вызова.

const reflectAngle = Phaser.Geom.Line.ReflectAngle(this.aimLine, this.reflectingLine);

3. **Вывод угла:** Полученный угол конвертируется в градусы и выводится в текстовое поле.

this.text.setText(`Reflect Angle: ${Phaser.Math.RadToDeg(reflectAngle)}`);

4. **Подготовка к рисованию:** Графический контекст очищается для рисования нового кадра.

this.graphics.clear();

Отрисовка траекторий

После расчетов наступает этап визуализации.

1. **Рисуем падающий луч:** Он отображается стилем по умолчанию (фиолетовый).

this.graphics.strokeLineShape(this.aimLine);

2. **Рисуем отражающую поверхность:** Устанавливается новый стиль линии (синий) и рисуется reflectingLine.

this.graphics.lineStyle(4, 0x0000aa);
this.graphics.strokeLineShape(this.reflectingLine);

3. **Создаем и рисуем отраженный луч:** Здесь используется два шага. Сначала мы получаем длину исходного падающего луча (aimLine). Затем метод Phaser.Geom.Line.SetToAngle перестраивает линию reflectedLine: задает ее начальную точку, направление (вычисленный reflectAngle) и длину. Это создает геометрически правильный отраженный луч. Наконец, он рисуется зеленым цветом.

const length = Phaser.Geom.Line.Length(this.aimLine);
Phaser.Geom.Line.SetToAngle(this.reflectedLine, 400, 300, reflectAngle, length);

this.graphics.lineStyle(2, 0x00aa00);
this.graphics.strokeLineShape(this.reflectedLine);

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

Это стандартный код инициализации Phaser 3 игры. Он создает конфигурационный объект с размерами холста, типом рендерера и ссылкой на нашу сцену Example, после чего инстанцирует игру.

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

const game = new Phaser.Game(config);

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

Методы Phaser.Geom.Line.ReflectAngle и SetToAngle предоставляют мощный и лаконичный способ работы с векторной геометрией отражений. Это избавляет разработчика от необходимости самостоятельно реализовывать формулы. Для экспериментов попробуйте: изменить начальные точки линий, сделать отражатель статичным или управляемым с клавиатуры, а также применить полученный угол reflectAngle для реального движения спрайта с помощью физического тела (body.setVelocity или body.velocity), создав простую игру с рикошетами.