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

Векторная математика — основа многих игровых механик, от управления персонажем до расчёта траекторий. Метод `.project()` класса `Phaser.Math.Vector2` позволяет спроецировать один вектор на другой, что полезно для создания теней, отражений, определения ближайшей точки на линии или расчёта силы, действующей в определённом направлении. Эта статья наглядно показывает, как работает проекция векторов в реальном времени, реагируя на движение курсора.

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

Живой запуск

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

Исходный код


class Example extends Phaser.Scene
{
    angle = 0;
    projectedPoint;
    point2;
    point;
    graphics;

    create ()
    {
        this.graphics = this.add.graphics({ lineStyle: { width: 2, color: 0x2266aa }, fillStyle: { color: 0xaa0000 } });

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

        this.projectedPoint = this.point2.clone().project(this.point);

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

            this.point2.copy(pointer);

            this.point2.x -= 400;
            this.point2.y -= 300;
        });
    }

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

        this.angle += 0.005;

        // vector starting at 0/0
        this.point.setTo(Math.cos(this.angle) * 250, Math.sin(this.angle) * 250);

        // drawn from the center (as if center was 0/0)
        this.graphics.lineBetween(400, 300, 400 + this.point.x, 300 + this.point.y);

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

        this.projectedPoint = this.point2.clone().project(this.point);

        // move relative to center
        this.projectedPoint.x += 400;
        this.projectedPoint.y += 300;

        this.graphics.fillPointShape(this.projectedPoint, 15);

        this.graphics.lineStyle(1, 0xaa0000);
        this.graphics.lineBetween(this.point2.x + 400, this.point2.y + 300, this.projectedPoint.x, this.projectedPoint.y);
    }
}

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

const game = new Phaser.Game(config);

Суть проекции вектора

Проекция вектора `aна векторb— это вектор, показывающий, насколькоa"ложится" на направлениеb. Его можно представить как тень или отражение одного вектора на другой. В Phaser для этого используется метод.project()`.

let projectedVector = vectorA.clone().project(vectorB);

Метод модифицирует вектор, на котором вызывается, превращая его в проекцию вектора-аргумента. Поэтому обычно используют клон исходного вектора, чтобы не потерять исходные данные. Визуально это точка на линии вектора `b, ближайшая к концу вектораa`.

Настройка сцены и векторов

В примере создаётся вращающийся вектор (this.point) и вектор, следующий за курсором (this.point2). Оба вектора заданы относительно центра экрана (координаты [400, 300]), что упрощает расчёты.

create() {
    this.graphics = this.add.graphics({ lineStyle: { width: 2, color: 0x2266aa }, fillStyle: { color: 0xaa0000 } });
    this.point = new Phaser.Math.Vector2(250, 0);
    this.point2 = new Phaser.Math.Vector2(250, 0);
    this.projectedPoint = this.point2.clone().project(this.point);
}

Графический объект this.graphics будет рисовать линии и точки. Обработчик события pointermove обновляет this.point2, переводя координаты курсора в систему отсчёта с центром в (400, 300).

Алгоритм обновления и визуализация

В методе update() происходит анимация и перерисовка. Вращающийся вектор this.point задаётся через синус и косинус.

this.angle += 0.005;
this.point.setTo(Math.cos(this.angle) * 250, Math.sin(this.angle) * 250);

Затем рисуются оба вектора, выходящие из центра экрана. Ключевой момент — вычисление проекции вектора курсора на вращающийся вектор.

this.projectedPoint = this.point2.clone().project(this.point);
this.projectedPoint.x += 400;
this.projectedPoint.y += 300;

Поскольку this.point2 и this.point заданы относительно центра, результат проекции также получается в этой системе. Перед отрисовкой его координаты возвращаются в глобальные, прибавляя смещение центра (400, 300).

Интерпретация результата

Спроецированная точка отображается красным кругом. Зелёная линия — это вектор от курсора (this.point2) до его проекции на синий вектор. Эта линия всегда перпендикулярна синему вектору, что и является геометрическим свойством проекции: кратчайшее расстояние от точки до линии.

this.graphics.fillPointShape(this.projectedPoint, 15);
this.graphics.lineStyle(1, 0xaa0000);
this.graphics.lineBetween(this.point2.x + 400, this.point2.y + 300, this.projectedPoint.x, this.projectedPoint.y);

Таким образом, визуализация помогает понять, что проекция — это точка на синей линии, куда "падает" перпендикуляр из точки курсора.

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

Метод .project() — мощный инструмент для работы с векторами, который можно использовать для расчёта отражения снаряда от поверхности, определения силы, толкающей объект вдоль дороги, или нахождения ближайшей к игроку точки на заданном пути. Попробуйте изменить пример: спроецируйте не вектор курсора, а, например, скорость одного объекта на направление к другому, чтобы смоделировать погоню, или используйте проекцию для создания простых теней от объектов на наклонных плоскостях.