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

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

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

Живой запуск

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

Исходный код


class Example extends Phaser.Scene
{
    cursor;
    graphics;
    group;

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

    create ()
    {
        this.group = this.physics.add.group({
            defaultKey: 'block',
            bounceX: 1,
            bounceY: 1,
            collideWorldBounds: true
        });

        this.group.create(100, 200).setVelocity(100, 200);
        this.group.create(500, 200).setVelocity(-100, -100);
        this.group.create(300, 400).setVelocity(60, 100);
        this.group.create(600, 300).setVelocity(-30, -50);

        this.graphics = this.add.graphics();

        this.cursor = this.add.image(400, 300, 'cursor');

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

    }

    update ()
    {
        const closest = this.physics.closest(this.cursor);
        const furthest = this.physics.furthest(this.cursor);

        this.graphics.clear()
            .lineStyle(2, 0xff3300)
            .lineBetween(closest.center.x, closest.center.y, this.cursor.x, this.cursor.y)
            .lineStyle(2, 0x0099ff)
            .lineBetween(furthest.center.x, furthest.center.y, this.cursor.x, this.cursor.y);
    }
}

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

const game = new Phaser.Game(config);

Подготовка сцены и объектов

В методе preload загружаются два спрайта: блок и курсор. В create инициализируется физическая группа (PhysicsGroup). Группа настраивается с параметрами отскока и столкновения с границами мира, что сразу делает её объекты динамическими телами Arcade.

this.group = this.physics.add.group({
    defaultKey: 'block',
    bounceX: 1,
    bounceY: 1,
    collideWorldBounds: true
});

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

this.group.create(100, 200).setVelocity(100, 200);
this.group.create(500, 200).setVelocity(-100, -100);
this.group.create(300, 400).setVelocity(60, 100);
this.group.create(600, 300).setVelocity(-30, -50);

Создаётся объект Graphics для отрисовки линий и спрайт курсора, который следует за указателем мыши, обновляя свою позицию по событию pointermove.

Магия методов closest и furthest

Вся логика поиска объектов заключена в методе update. Методы this.physics.closest() и this.physics.furthest() принимают в качестве аргумента целевую точку — в нашем случае это спрайт курсора (this.cursor). Движок автоматически перебирает все тела в текущем физическом мире Arcade (включая тела, созданные через группу) и находит среди них ближайшее и дальнее относительно центра переданного объекта.

const closest = this.physics.closest(this.cursor);
const furthest = this.physics.furthest(this.cursor);

Важно: методы возвращают не просто спрайт, а ссылку на физическое тело (Arcade Physics Body), что позволяет сразу обращаться к его свойствам, таким как center. Эти методы работают только при активном физическом движке Arcade.

Визуализация результата

Чтобы результат поиска был очевиден, от курсора к найденным объектам рисуются линии. Объект Graphics очищается каждый кадр, затем для ближайшего объекта рисуется красная линия, а для дальнего — синяя. Координаты для рисования берутся из центров физических тел (closest.center).

this.graphics.clear()
    .lineStyle(2, 0xff3300)
    .lineBetween(closest.center.x, closest.center.y, this.cursor.x, this.cursor.y)
    .lineStyle(2, 0x0099ff)
    .lineBetween(furthest.center.x, furthest.center.y, this.cursor.x, this.cursor.y);

Использование center тела, а не позиции спрайта, гарантирует, что линия будет проведена к точному центру массы объекта для физических расчётов, что особенно важно для объектов с нестандартным смещением (origin).

Конфигурация физического движка

Ключевой момент — активация физического движка Arcade в конфигурации игры. Без этого методы closest и furthest будут недоступны.

const config = {
    type: Phaser.AUTO,
    physics: {
        default: 'arcade', // Движок должен быть 'arcade'
        arcade: { debug: false }
    },
    scene: Example
};

Методы являются частью API менеджера физики (this.physics), который инициализируется именно при такой конфигурации. Они не будут работать с другими движками, например, Matter.js, без соответствующего плагина или реализации.

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

Методы closest и furthest — это мощные и лаконичные инструменты Phaser Arcade для пространственного анализа. Они избавляют разработчика от написания циклов и расчётов расстояний. Для экспериментов попробуйте

  1. передавать в методы не спрайт, а объект с координатами {x, y}
  2. использовать результат для автоматического наведения оружия врага на ближайшего игрока
  3. комбинировать поиск с фильтрацией, предварительно получая массив целей через this.physics.overlap() и находя среди них самый близкий объект