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

При разработке игр часто требуется проверять расстояния между объектами: для обнаружения столкновений, агрессии врагов или активации событий. Наивный подход с использованием полного расчета длины вектора через `Math.sqrt()` может стать бутылочным горлышком в производительности, особенно при сотнях проверок за кадр. В этой статье мы разберем пример из официальной документации Phaser, который демонстрирует, как использовать метод `lengthSq()` для оптимизации таких операций. Вы научитесь заменять тяжелые вычисления на их легковесные квадраты, сохраняя ту же логику, но увеличивая FPS вашей игры.

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

Живой запуск

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

Исходный код


class Example extends Phaser.Scene
{
    a = 0;
    points;
    point;
    graphics;

    create ()
    {
        this.graphics = this.add.graphics({ fillStyle: { color: 0x2266aa } });

        this.point = new Phaser.Math.Vector2();

        this.points = [];

        for (let i = 0; i < 45; i++)
        {
            const x = Math.random() * 800;
            const y = Math.random() * 600;
            this.points.push(new Phaser.Math.Vector2(x, y));
        }
    }

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

        this.a += 0.005;

        this.point.x = 400 - Math.cos(this.a) * 400;
        this.point.y = 300 - Math.sin(this.a * 4) * 300;

        for (let i = 0; i < this.points.length; i++)
        {
            const temp = this.point.clone();

            temp.x -= this.points[i].x;
            temp.y -= this.points[i].y;
            const magnitudeSquared = temp.lengthSq();

            if (magnitudeSquared < 30 * 30)
            {
                this.graphics.lineStyle(2, 0xaa0000);
            }
            else
            {
                this.graphics.lineStyle(2, 0x0000aa);
            }

            this.graphics.strokeCircle(this.points[i].x, this.points[i].y, 30);
        }

        this.graphics.fillPointShape(this.point, 5);
    }
}

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

const game = new Phaser.Game(config);

Суть оптимизации: зачем нужен lengthSq()

Основное правило простое: сравнение расстояний часто не требует знания точной длины. Достаточно сравнить квадраты этих расстояний.

Для расчета длины вектора `vс компонентами(x, y)используется формула:Math.sqrt(x*x + y*y). Операция взятия квадратного корня (Math.sqrt`) относительно затратна для процессора.

Метод Phaser.Math.Vector2.lengthSq() возвращает квадрат длины вектора, то есть просто сумму квадратов его компонент: x*x + y*y. Это быстро.

Если нам нужно проверить, меньше ли расстояние `dчем радиусr, вместоd < rмы можем сравнить квадраты:dSq < r*r`. Результат будет математически идентичен, но вычисления станут быстрее.

Разбор примера: динамическая визуализация

Рассмотрим исходный код примера. В нем создается набор из 45 случайных точек (this.points) и одна управляемая точка (this.point), которая движется по сложной траектории.

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

Ключевой момент — проверка расстояния. Вместо вычисления полного расстояния между точками, код использует вектор temp и метод lengthSq().

const temp = this.point.clone();
temp.x -= this.points[i].x;
temp.y -= this.points[i].y;
const magnitudeSquared = temp.lengthSq();

if (magnitudeSquared < 30 * 30)
{
    this.graphics.lineStyle(2, 0xaa0000); // Красный цвет, если близко
}
else
{
    this.graphics.lineStyle(2, 0x0000aa); // Синий цвет, если далеко
}

Здесь magnitudeSquared — это квадрат расстояния между точками. Мы сравниваем его с квадратом радиуса (30 * 30 = 900). Это позволяет избежать вызова Math.sqrt для каждой из 45 точек в каждом кадре.

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

Где это пригодится в реальной разработке?

* **Определение радиуса атаки:** Проверка, находится ли враг в зоне поражения оружия игрока. * **Аггрешен мобов:** Определение, увидел ли противник главного героя, на основе расстояния. * **Сбор предметов:** Проверка, подошел ли игрок достаточно близко к монетке или аптечке. * **Генерация звука:** Изменение громкости или панорамирования звука в зависимости от дистанции до источника.

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

Вот как может выглядеть проверка радиуса атаки меча:

// player и enemy — объекты с векторами position (Phaser.Math.Vector2)
const attackRadius = 100;
const delta = player.position.clone().subtract(enemy.position);
const distanceSq = delta.lengthSq();

if (distanceSq < attackRadius * attackRadius) {
    // Враг достигнут ударом!
    enemy.takeDamage();
}

Когда НЕ стоит использовать lengthSq()

Оптимизация — это не серебряная пуля. Метод lengthSq() полезен только для сравнений. Если вам нужна именно числовая величина расстояния (например, чтобы отобразить его на экране, интерполировать движение или рассчитать урон, пропорциональный дистанции), то без Math.sqrt не обойтись.

Phaser предоставляет оба метода для объекта Vector2: * .length() — возвращает точную длину (использует Math.sqrt). * .lengthSq() — возвращает квадрат длины (быстрый).

Используйте их соответственно задаче. Лучшая практика — сначала писать логику с понятным .length(), а затем, при необходимости оптимизации в критических по производительности местах, заменять сравнения на работу с .lengthSq().

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

Использование Vector2.lengthSq() вместо Vector2.length() для проверки расстояний — это простая и эффективная микрооптимизация, которая может дать заметный прирост производительности в сценах с массовыми расчетами. Для экспериментов попробуйте в примере увеличить количество точек с 45 до 500 и сравните FPS при использовании length() и lengthSq(). Также можно изменить логику: например, подсвечивать точки, которые находятся не *ближе*, а *дальше* определенного расстояния от движущейся точки.