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

В разработке игр часто возникает задача взаимодействия с группой объектов в определённой зоне: активация ловушек, подсветка целей или массовое наложение эффектов. Ручной перебор всех спрайтов на сцене неэффективен. В этой статье мы разберём пример использования метода `physics.overlapRect`, который позволяет мгновенно получить все физические тела, пересекающие заданный прямоугольник, используя оптимизированные внутренние структуры движка Arcade Physics. Этот подход критически важен для производительности игр с большим количеством объектов.

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

Живой запуск

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

Исходный код


class Example extends Phaser.Scene
{
    rect;
    blocks = [];

    preload ()
    {
        this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
        this.load.image('mushroom', 'assets/sprites/mushroom16x16.png');
    }

    create ()
    {
        const spriteBounds = Phaser.Geom.Rectangle.Inflate(Phaser.Geom.Rectangle.Clone(this.physics.world.bounds), -20, -20);

        for (let i = 0; i < 500; i++)
        {
            const pos = Phaser.Geom.Rectangle.Random(spriteBounds);

            const block = this.physics.add.sprite(pos.x, pos.y, 'mushroom');

            block.setBounce(1).setCollideWorldBounds(true);

            Phaser.Math.RandomXY(block.body.velocity, 100);

            this.blocks.push(block);
        }

        this.rect = this.add.rectangle(400, 300, 300, 200).setStrokeStyle(2, 0xffff00);

        this.input.on('pointermove', (pointer) =>
        {
            this.rect.copyPosition(pointer);
        });
    }

    update ()
    {
        Phaser.Actions.SetAlpha(this.blocks, 0.5);

        const { left, top, width, height } = this.rect.getBounds();

        const bodiesInRect = this.physics.overlapRect(left, top, width, height);

        Phaser.Actions.SetAlpha(bodiesInRect.map(body => body.gameObject), 1);
    }
}

const config = {
    type: Phaser.AUTO,
    width: 800,
    height: 600,
    parent: 'phaser-example',
    physics: {
        default: 'arcade',
        arcade: {
            gravity: { y: 100 },
            debug: false
        }
    },
    scene: Example
};

const game = new Phaser.Game(config);

Подготовка сцены: создание множества физических тел

В методе create мы создаём 500 физических спрайтов, которые будут свободно перемещаться по сцене. Это типичный сценарий для частиц, врагов или предметов.

Сначала мы вычисляем безопасную область внутри границ физического мира, чтобы спрайты не появлялись вплотную к краям. Для этого используется утилитарный метод Phaser.Geom.Rectangle.Inflate.

const spriteBounds = Phaser.Geom.Rectangle.Inflate(Phaser.Geom.Rectangle.Clone(this.physics.world.bounds), -20, -20);

Затем в цикле мы размещаем каждый спрайт в случайной точке (Random) внутри этой области, добавляем ему физическое тело и задаём начальную случайную скорость.

const pos = Phaser.Geom.Rectangle.Random(spriteBounds);
const block = this.physics.add.sprite(pos.x, pos.y, 'mushroom');
block.setBounce(1).setCollideWorldBounds(true);
Phaser.Math.RandomXY(block.body.velocity, 100);
this.blocks.push(block);

Также мы создаём жёлтый прямоугольник (this.rect), который будет следовать за курсором. Он и будет нашей зоной поиска.

Ядро логики: поиск тел в прямоугольнике

Вся магия происходит в методе update. Каждый кадр мы сначала устанавливаем всем блокам (this.blocks) полупрозрачность (alpha = 0.5), чтобы визуально выделить те, что находятся в зоне.

Phaser.Actions.SetAlpha(this.blocks, 0.5);

Затем мы получаем координаты и размеры нашего прямоугольника-курсора с помощью метода getBounds().

const { left, top, width, height } = this.rect.getBounds();

Ключевой вызов — this.physics.overlapRect. Этот метод принимает координаты левого верхнего угла, ширину и высоту прямоугольника и возвращает массив физических тел (Arcade.Body), которые в данный момент пересекают эту область. Он работает очень быстро, так как использует внутренние оптимизации движка Arcade Physics для пространственного разбиения.

const bodiesInRect = this.physics.overlapRect(left, top, width, height);

Наконец, мы проходим по найденным телам, получаем их игровые объекты (gameObject) и делаем их полностью непрозрачными.

Phaser.Actions.SetAlpha(bodiesInRect.map(body => body.gameObject), 1);

Практическое применение и нюансы

Метод overlapRect — это мощный инструмент для игровой механики. Вот несколько идей для использования: * **Область эффекта (AoE):** Нанесение урона всем врагам в зоне взрыва. * **Подсветка или выбор:** В стратегиях — выделение юнитов в прямоугольной области. * **Активация триггеров:** Проверка, находится ли игрок в зоне, чтобы открыть дверь или запустить диалог.

Важно помнить: 1. Метод возвращает массив тел (Arcade.Body), а не игровых объектов. Чтобы получить спрайт, используйте свойство body.gameObject. 2. Поиск работает только с телами, у которых включена проверка на перекрытие (overlap). В нашем примере это включено по умолчанию, так как мы создавали тела через this.physics.add.sprite. 3. Метод учитывает текущее положение тел в физическом мире на момент вызова. Это "мгновенный снимок" состояния.

Для статичных или реже меняющихся зон результат можно кэшировать и пересчитывать не каждый кадр, а, например, раз в полсекунды.

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

Использование physics.overlapRect — это оптимальный и производительный способ взаимодействия с группой объектов в заданной области в Phaser 3. Он избавляет от необходимости вручную перебирать и проверять координаты каждого спрайта. **Идеи для экспериментов:** 1. Измените логику: пусть тела в прямоугольнике не подсвечиваются, а, наоборот, получают импульс (setVelocity) от центра области, создавая эффект "отталкивающего поля". 2. Сделайте прямоугольник не привязанным к курсору, а движущимся по собственной траектории, создав "сканирующий луч". 3. Используйте не прямоугольник, а круг, комбинируя overlapRect с дополнительной проверкой расстояния до центра или используя метод distance у тел.