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

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

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

Живой запуск

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

Исходный код


class Example extends Phaser.Scene
{
    create ()
    {
        const graphics = this.add.graphics({ lineStyle: { width: 4, color: 0xaa00aa } });

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

        const textX = this.add.text(50, 50, '');
        const textY = this.add.text(50, 75, '');

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

            line.x2 = pointer.x;
            line.y2 = pointer.y;

            redraw();
        });

        redraw();

        function redraw ()
        {
            graphics.clear();

            graphics.strokeLineShape(line);

            const normalX = Phaser.Geom.Line.NormalX(line);
            const normalY = Phaser.Geom.Line.NormalY(line);

            graphics.lineStyle(2, 0xaa0000);
            graphics.lineBetween(400, 300, 400 + normalX * 100, 300);

            graphics.lineStyle(2, 0x00aa00);
            graphics.lineBetween(400, 300, 400, 300 + normalY * 100);

            textX.setText(`Line Normal X: ${normalX}`);
            textY.setText(`Line Normal Y: ${normalY}`);
        }
    }
}

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

const game = new Phaser.Game(config);

Что такое нормаль линии?

Нормаль линии — это единичный вектор (длиной 1), перпендикулярный этой линии. Он всегда направлен вправо от начальной точки линии к ее конечной точке при стандартном математическом представлении.

Вектор нормали состоит из двух компонентов: NormalX и NormalY. Эти значения лежат в диапазоне от -1 до 1 и описывают направление. Например, нормаль (1, 0) указывает строго вправо, (0, 1) — строго вниз, а (-0.707, 0.707) — на 45 градусов вверх-влево.

В физике игр именно этот вектор используется для расчета отскока: чтобы объект отскочил от стены, его скорость отражается относительно нормали к поверхности в точке столкновения.

Разбор примера: создание сцены и линии

В примере создается базовая сцена и линия, второй конец которой привязан к курсору мыши. Это позволяет интерактивно менять угол и наблюдать за изменением нормали.

class Example extends Phaser.Scene
{
    create ()
    {
        const graphics = this.add.graphics({ lineStyle: { width: 4, color: 0xaa00aa } });
        const line = new Phaser.Geom.Line(400, 300, 550, 300);

Здесь создается объект Graphics для отрисовки и линия с началом в точке (400, 300) и начальным концом в (550, 300). Линия будет отрисована фиолетовым цветом (0xaa00aa). Создаются также два текстовых объекта для вывода числовых значений нормали.

this.input.on('pointermove', pointer =>
        {
            line.x2 = pointer.x;
            line.y2 = pointer.y;
            redraw();
        });

Обработчик события pointermove обновляет координаты второй точки линии (x2, y2) текущей позицией курсора и вызывает функцию redraw() для обновления изображения на экране.

Вычисление и отрисовка нормали

Ключевая логика происходит в функции redraw. Она очищает холст, рисует обновленную линию, вычисляет ее нормаль и визуализирует компоненты нормали в виде отдельных векторов.

function redraw ()
        {
            graphics.clear();
            graphics.strokeLineShape(line);

            const normalX = Phaser.Geom.Line.NormalX(line);
            const normalY = Phaser.Geom.Line.NormalY(line);

С помощью статических методов Phaser.Geom.Line.NormalX(line) и Phaser.Geom.Line.NormalY(line) вычисляются компоненты вектора нормали для текущей линии. Эти методы возвращают числа с плавающей запятой.

graphics.lineStyle(2, 0xaa0000);
            graphics.lineBetween(400, 300, 400 + normalX * 100, 300);

            graphics.lineStyle(2, 0x00aa00);
            graphics.lineBetween(400, 300, 400, 300 + normalY * 100);

Здесь происходит визуализация. Красная линия (0xaa0000) отображает X-компонент нормали. Она рисуется горизонтально от начальной точки линии. Координата X конечной точки рассчитывается как 400 + normalX * 100. Умножение на 100 — это масштабирование, чтобы вектор нормали (длиной 1) было хорошо видно на экране. Аналогично зеленая линия (0x00aa00) отображает Y-компонент вертикально. Это разложение единичного вектора нормали по осям.

textX.setText(`Line Normal X: ${normalX}`);
            textY.setText(`Line Normal Y: ${normalY}`);
        }
    }
}

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

Пример завершается стандартной для Phaser конфигурацией игры, которая создает экземпляр сцены, описанной выше.

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

const game = new Phaser.Game(config);

Эта конфигурация создает холст размером 800x600 пикселей и инициализирует нашу сцену Example.

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

Вычисление нормали линии — простая, но мощная операция, открывающая путь к созданию сложной игровой физики. Используя Phaser.Geom.Line.NormalX и NormalY, вы можете реализовать реалистичный отскок мяча от наклонной поверхности или рассчитать силу трения, действующую вдоль препятствия. **Идеи для экспериментов:** 1. Создайте спрайт-шарик и заставьте его отскакивать от нарисованной линии, используя нормаль для расчета нового вектора скорости. 2. Реализуйте "скольжение" объекта вдоль сложного контура: когда объект сталкивается с линией, обнулите компонент скорости, направленный вдоль нормали, оставив только касательную составляющую. 3. Используйте нормаль для визуальных эффектов, например, для расчета направления и длины блика на глянцевой поверхности, представленной линией.