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

Векторная математика — ключ к реализации плавных движений, вращений и столкновений в играх. Понимание того, как изменять направление векторов, открывает возможности для создания реалистичной физики и поведения объектов. В этом примере мы разберем метод `normalizeRightHand()` из класса `Phaser.Math.Vector2`, который мгновенно вычисляет перпендикулярный вектор, повернутый на 90 градусов вправо от исходного, и посмотрим, как его можно использовать для визуализации и игровой логики.

Версия 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.normalizeRightHand();

        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);

Что делает normalizeRightHand()?

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

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

// Исходный вектор
let direction = new Phaser.Math.Vector2(10, 0); // Смотрит направо
// Применяем метод
direction.normalizeRightHand();
// Теперь вектор равен (0, -1) — смотрит вниз (перпендикулярно вправо)

Разбор примера: отслеживание мыши

В данном примере создается сцена с графикой (Graphics) и одним вектором this.point. Координаты этого вектора привязаны к положению указателя мыши относительно центра экрана.

При каждом движении мыши (pointermove) вычисляется смещение (pointer.x - 400, pointer.y - 300) от центра экрана (точка 400, 300). Это смещение и есть наш вектор. Затем вызывается функция redraw() для обновления изображения.

this.input.on('pointermove', pointer =>
{
    //  Вектор от центра к курсору
    this.point.x = pointer.x - 400;
    this.point.y = pointer.y - 300;

    this.redraw();
});

Визуализация перпендикуляра

Функция redraw() отвечает за всю графику. Сначала она очищает холст с помощью this.graphics.clear(). Затем рисует первую линию от центра экрана до текущего положения вектора (т.е. до курсора). Это исходный вектор.

Далее вызывается ключевой метод this.point.normalizeRightHand(). Он модифицирует сам вектор this.point — теперь это уже не вектор к курсору, а его перпендикулярный, единичный вариант. После смены стиля линии рисуется вторая линия от центра до нового положения вектора, наглядно демонстрируя результат поворота.

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

// Меняем сам вектор this.point на его перпендикулярную версию
this.point.normalizeRightHand();

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

Важно: поскольку метод изменяет (мутирует) исходный вектор, после его вызова вектор this.point больше не указывает на курсор. Для сохранения исходного направления потребовалось бы работать с его копией.

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

Зачем это нужно в реальной разработке? Представьте, что у вас есть снаряд, летящий с вектором скорости velocity. Используя normalizeRightHand(), вы можете мгновенно получить вектор, направленный, например, вправо от его траектории.

Это можно использовать для: 1. **Расчета бокового смещения:** Создания эффекта "двойного выстрела" или размещения спутников по бокам от корабля. 2. **Построения нормалей:** Для вычисления вектора отскока от поверхности примитивной коллизии. 3. **Математических операций:** Как базис для создания локальной системы координат объекта.

// Пример: смещение двух пуль вправо и влево от направления выстрела
let fireDirection = new Phaser.Math.Vector2(1, 0).normalize(); // Основное направление
let rightPerp = fireDirection.clone().normalizeRightHand(); // Перпендикуляр вправо
let leftPerp = fireDirection.clone().normalizeLeftHand();   // Перпендикуляр влево

// Вычисляем позиции для спавна пуль по бокам
let bullet1Pos = shipPosition.clone().add(rightPerp.scale(20));
let bullet2Pos = shipPosition.clone().add(leftPerp.scale(20));

Помните, что для операций без изменения исходного вектора нужно использовать .clone().

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

Метод Vector2.normalizeRightHand() — это мощный и компактный инструмент для работы с направлениями в 2D-пространстве. Он идеально подходит для задач, где требуется быстро получить перпендикулярный вектор фиксированной длины (единичный). Для экспериментов попробуйте

  1. создать спутники, которые вращаются вокруг корабля, используя перпендикулярные векторы для расчета позиций
  2. реализовать простой отскот мяча от наклонной поверхности, рассчитанной через нормаль
  3. визуализировать систему локальных координат игрового персонажа