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

Работа с векторами — основа для множества игровых механик: от простого поворота объекта до сложного расчёта отскоков и траекторий. В этой статье мы разберём, как использовать метод `normalizeLeftHand()` (также известный как `perp()`) класса `Phaser.Math.Vector2` для быстрого получения перпендикулярного вектора. Этот приём незаменим при вычислении нормалей для столкновений, создании эффектов разлёта частиц или реализации управления, основанного на векторах.

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

Живой запуск

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

Исходный код


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

        this.point = new Phaser.Math.Vector2(250, -250);

        this.input.on('pointermove', pointer =>
        {
            //  Set relative to center
            this.point.x = pointer.x - 400;
            this.point.y = pointer.y - 300;

            this.redraw();
        });

        this.redraw();
    }

    redraw ()
    {
        this.graphics.clear();

        this.graphics.lineBetween(400, 300, 400 + this.point.x, 300 + this.point.y);

        // rotates the point around 0/0 at 90 degrees towards right
        // the result is a vector perpendicular to the original vector
        this.point.normalizeLeftHand();

        this.graphics.lineStyle(2, 0x00aa00);
        this.graphics.lineBetween(400, 300, 400 + this.point.x, 300 + this.point.y);
    }
}

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

const game = new Phaser.Game(config);

Что делает метод normalizeLeftHand?

Метод normalizeLeftHand() (или его синоним perp()) выполняет две операции одновременно. Во-первых, он нормализует вектор, то есть приводит его к единичной длине, сохраняя направление. Во-вторых, и это главное, он поворачивает этот единичный вектор на 90 градусов против часовой стрелки (в левую сторону).

В результате из исходного вектора мы мгновенно получаем вектор, перпендикулярный ему, что является частой и важной операцией в физике 2D-игр.

Давайте посмотрим на его работу на простом примере. Исходный вектор (3, 0) после применения метода превратится в (0, -1).

let vec = new Phaser.Math.Vector2(3, 0);
vec.normalizeLeftHand(); // Теперь vec равен (0, -1)

Разбираем пример: интерактивный перпендикуляр

Рассмотренный пример создаёт интерактивную визуализацию. Пользователь двигает курсор, а программа в реальном времени рисует исходный вектор и перпендикулярный к нему.

В методе create() инициализируются графика, начальная точка-вектор и обработчик события движения мыши.

create ()
{
    // Создаём объект Graphics для рисования линий
    this.graphics = this.add.graphics({ lineStyle: { width: 3, color: 0x2266aa } });

    // Создаём вектор, который будет меняться от позиции курсора
    this.point = new Phaser.Math.Vector2(250, -250);

    // При движении мыши обновляем координаты вектора
    this.input.on('pointermove', pointer =>
    {
        // Координаты считаются относительно центра экрана (400, 300)
        this.point.x = pointer.x - 400;
        this.point.y = pointer.y - 300;
        this.redraw(); // Перерисовываем сцену
    });
    this.redraw(); // Первоначальная отрисовка
}

Ключевой момент — координаты вектора this.point хранятся не в абсолютных координатах экрана, а относительно центра (400, 300). Это упрощает математические операции, так как вектор начинается в условной точке (0, 0).

Логика отрисовки в методе redraw

Метод redraw() очищает холст и рисует две линии: исходный вектор и перпендикулярный к нему.

redraw ()
{
    // Очищаем предыдущие рисунки
    this.graphics.clear();

    // Рисуем ИСХОДНЫЙ вектор (синяя линия) от центра к позиции курсора
    this.graphics.lineBetween(400, 300, 400 + this.point.x, 300 + this.point.y);

    // Меняем вектор this.point! Теперь он станет перпендикулярным единичным вектором.
    this.point.normalizeLeftHand();

    // Меняем стиль линии на зелёный и рисуем ПЕРПЕНДИКУЛЯРНЫЙ вектор
    this.graphics.lineStyle(2, 0x00aa00);
    this.graphics.lineBetween(400, 300, 400 + this.point.x, 300 + this.point.y);
}

Важно понимать последовательность: сначала мы используем исходные координаты this.point для рисования синей линии. Затем применяем this.point.normalizeLeftHand(), что **изменяет сам вектор** this.point. После этого вызова this.point уже не указывает на курсор, а является перпендикулярным единичным вектором. Именно его мы и рисуем зелёной линией из того же центра.

Практическое применение в играх

Перпендикулярный вектор (нормаль) находит множество применений.

1. **Физика столкновений:** При столкновении шара со стеной вектор отскока рассчитывается через нормаль к поверхности. normalizeLeftHand() помогает быстро её найти.

// Допустим, вектор стены направлен вдоль оси X (1, 0)
    let wallDirection = new Phaser.Math.Vector2(1, 0);
    // Нормаль к стене (направленная вверх)
    let wallNormal = wallDirection.clone().normalizeLeftHand(); // (0, -1)

2. **Движение "боком":** Если у игрока есть вектор взгляда forward, то вектор движения вправо/влево — это его перпендикуляр.

let forward = new Phaser.Math.Vector2(0, -1); // Смотрит вверх
    let right = forward.clone().normalizeLeftHand(); // Смотрит вправо (1, 0)

3. **Разлёт частиц:** При взрыве частицы разлетаются во все стороны. Перпендикуляр к вектору от центра взрыва даёт одно из возможных направлений.

normalizeLeftHand vs perp vs normalizeRightHand

В Phaser 3 для одной и той же операции есть несколько имён, что может сбить с толку.

* normalizeLeftHand() и perp() — это **один и тот же метод**. Он поворачивает вектор на 90° **против** часовой стрелки. * normalizeRightHand() (или rperp()) делает обратное: нормализует и поворачивает на 90° **по** часовой стрелке.

Выбор метода зависит от нужного вам направления поворота в вашей системе координат. В 2D Phaser ось Y направлена **вниз**, поэтому «левый» поворот (против часовой стрелки) визуально может выглядеть как поворот вправо, если вектор указывает вниз. Всегда проверяйте направление в своём конкретном случае.

let v = new Phaser.Math.Vector2(0, 1); // Вектор, смотрящий вниз
v.normalizeLeftHand();  // Результат: (1, 0)  (поворот против ЧЧ -> вправо)
v.normalizeRightHand(); // Результат: (-1, 0) (поворот по ЧЧ -> влево)

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

Метод normalizeLeftHand() (он же perp()) — это мощный и лаконичный инструмент для работы с векторами в Phaser 3. Он позволяет в одну строку кода получить перпендикулярный единичный вектор, что критически важно для расчётов в физике, управлении и визуальных эффектах. **Идеи для экспериментов:** 1. Создайте шар, который отскакивает от наклонной платформы, используя перпендикуляр к её вектору как нормаль. 2. Реализуйте космический корабль, который может двигаться не только вперёд-назад, но и строго вбок (стрифинг), используя перпендикуляр к вектору направления. 3. Визуализируйте поле нормалей для сложной фигуры, рисуя маленькие перпендикулярные линии вдоль её контура.