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

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

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

Живой запуск

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

Исходный код


class Example extends Phaser.Scene
{
    normal;
    line;
    graphics;

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

        this.line = new Phaser.Geom.Line(390, 300, 410, 300);

        // if we omit a parameter, new Point instance will be created and returned
        this.normal = Phaser.Geom.Line.GetNormal(this.line);
    }

    update ()
    {
        if (this.line.y2 > -150)
        {
            this.graphics.strokeLineShape(this.line);

            // normal is a directly perpendicular vector to supplied line

            // this moves the second line point 15px away in perpendicular direction

            this.line.x2 += this.normal.x * 15;
            this.line.y2 += this.normal.y * 15;

            // we can also supply an instance of Point that will be modified
            Phaser.Geom.Line.GetNormal(this.line, this.normal);
        }
    }
}

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

const game = new Phaser.Game(config);

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

Нормаль к линии — это единичный вектор (длиной 1), который направлен строго перпендикулярно этой линии. В 2D у линии есть две возможные нормали, направленные в противоположные стороны. Phaser.Geom.Line.GetNormal() возвращает одну из них (конкретное направление зависит от внутренней реализации).

Этот вектор незаменим для расчетов, где важно направление «от поверхности»: - Отражение объекта от линии (как мяч от стены). - Движение объекта вдоль линии (скольжение). - Определение, с какой стороны от линии находится точка.

Получение нормали: теория и код

Метод GetNormal — статический метод класса Phaser.Geom.Line. Он принимает линию и, опционально, объект Phaser.Geom.Point для результата.

// Создаем линию от точки (x1, y1) до точки (x2, y2)
this.line = new Phaser.Geom.Line(390, 300, 410, 300);

// Вариант 1: Получить новый объект Point с нормалью
this.normal = Phaser.Geom.Line.GetNormal(this.line);

// Вариант 2: Перезаписать существующий объект Point (экономит память)
let myPoint = new Phaser.Geom.Point();
Phaser.Geom.Line.GetNormal(this.line, myPoint);
// Теперь myPoint содержит (x, y) нормали

В первом варианте функция создает и возвращает новый экземпляр Point. Во втором — перезаписывает координаты переданного объекта myPoint, что эффективнее при частых вызовах в цикле update().

Практика: визуализация и движение по нормали

Давайте рассмотрим, как пример использует нормаль для анимации. В create() мы получаем начальную нормаль для горизонтальной линии. В update() мы постепенно удлиняем линию, смещая её конечную точку в направлении нормали.

update ()
{
    if (this.line.y2 > -150)
    {
        this.graphics.strokeLineShape(this.line);

        // Смещаем конечную точку (x2, y2) на 15 пикселей в направлении нормали
        this.line.x2 += this.normal.x * 15;
        this.line.y2 += this.normal.y * 15;

        // Пересчитываем нормаль для НОВОГО положения линии
        // и записываем результат в существующий this.normal
        Phaser.Geom.Line.GetNormal(this.line, this.normal);
    }
}
Ключевые моменты:
1. `this.normal.x * 15` и `this.normal.y * 15` — это проекции смещения на 15 пикселей по осям X и Y. Так как нормаль — единичный вектор, умножение на скаляр (15) задает точное расстояние смещения.
2. После изменения линии (`this.line`) нормаль пересчитывается. Если этого не сделать, движение продолжится по старому направлению, даже когда линия уже повернулась.
3. Цикл останавливается, когда `y2` достигает -150, предотвращая бесконечную анимацию.

Применение в играх: отскок и скольжение

Вот как можно применить нормаль в игровой логике.

**Простое отражение вектора скорости:**

// Допустим, ball.velocity — это объект Phaser.Math.Vector2
// line — это линия, от которой происходит отскок
let normal = Phaser.Geom.Line.GetNormal(line);

// Формула отражения: newVelocity = velocity - 2 * (velocity · normal) * normal
let dotProduct = ball.velocity.x * normal.x + ball.velocity.y * normal.y;
ball.velocity.x = ball.velocity.x - 2 * dotProduct * normal.x;
ball.velocity.y = ball.velocity.y - 2 * dotProduct * normal.y;

**Скольжение вдоль стены (обнуление скорости вдоль нормали):**

// Если объект коснулся стены, оставляем только ту компоненту скорости,
// которая перпендикулярна нормали (т.е. параллельна стене)
let normal = Phaser.Geom.Line.GetNormal(wallLine);
let dotProduct = sprite.body.velocity.x * normal.x + sprite.body.velocity.y * normal.y;

// Вычитаем из скорости компоненту, направленную вдоль нормали (в стену)
sprite.body.velocity.x -= dotProduct * normal.x;
sprite.body.velocity.y -= dotProduct * normal.y;

Эти приемы работают с любыми системами координат и углами поворота линий.

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

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

  1. Создать лабиринт, где шарик отражается от его стен
  2. Заставить частицы системы «огонь» или «дым» двигаться перпендикулярно поверхности объекта
  3. Реализовать механику «прилипания» и скольжения персонажа по сложному рельефу, используя нормали сегментов платформы