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

При создании игр часто возникает задача генерировать объекты (врагов, бонусы, декорации) в определённой зоне, но с важным условием — они не должны появляться внутри запрещённых областей, например, внутри зданий или безопасных зон. Вручную рассчитывать такие позиции сложно и неэффективно. Метод `Phaser.Geom.Rectangle.RandomOutside` из Phaser 3 решает эту задачу элегантно. Он генерирует случайные точки внутри одного прямоугольника (`rectOuter`), но гарантированно вне другого (`rectInner`). Это мощный инструмент для процедурной генерации игрового пространства, который мы разберем на практическом примере.

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

Живой запуск

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

Исходный код


class Example extends Phaser.Scene
{
    graphics;
    rectInner;
    rectOuter;

    create ()
    {
        this.graphics = this.add.graphics({ lineStyle: { width: 1, color: 0x00ff00 }, fillStyle: { color: 0xff00ff }});

        this.rectOuter = new Phaser.Geom.Rectangle(50, 100, 600, 450);
        this.rectInner = new Phaser.Geom.Rectangle(250, 200, 300, 200);

        this.plotRandomPoints();

        this.time.addEvent({ delay: 1000, callback: () => this.plotRandomPoints, loop: true });
    }

    plotRandomPoints ()
    {
        this.graphics.clear();
        this.graphics.strokeRectShape(this.rectOuter);
        this.graphics.strokeRectShape(this.rectInner);

        for (let i = 0; i < 400; i++)
        {
            const p = Phaser.Geom.Rectangle.RandomOutside(this.rectOuter, this.rectInner);

            this.graphics.fillRect(p.x, p.y, 2, 2);
        }
    }
}

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

const game = new Phaser.Game(config);

Подготовка сцены и определение областей

В методе create() инициализируется сцена. Создаются два прямоугольника, которые определяют игровые зоны: внешняя граница (rectOuter) и внутренняя запрещенная область (rectInner).

Для визуализации используется объект Graphics. Он позволяет рисовать примитивы, такие как линии и залитые фигуры. В данном случае он настроен на рисование зеленых линий и заливку фиолетовым цветом.

this.graphics = this.add.graphics({ lineStyle: { width: 1, color: 0x00ff00 }, fillStyle: { color: 0xff00ff }});

this.rectOuter = new Phaser.Geom.Rectangle(50, 100, 600, 450);
this.rectInner = new Phaser.Geom.Rectangle(250, 200, 300, 200);

Генерация и отрисовка случайных точек

Основная логика заключена в методе plotRandomPoints(). Сначала холст Graphics очищается, и заново отрисовываются контуры обоих прямоугольников. Это нужно для обновления кадра.

Затем в цикле генерируются 400 точек. Ключевой вызов — Phaser.Geom.Rectangle.RandomOutside(this.rectOuter, this.rectInner). Этот статический метод возвращает объект точки (`p) со свойствамиxиy. Координаты точки всегда лежат внутриrectOuter, но за пределамиrectInner`.

Сгенерированная точка отрисовывается как маленький залитый прямоугольник размером 2x2 пикселя.

for (let i = 0; i < 400; i++)
{
    const p = Phaser.Geom.Rectangle.RandomOutside(this.rectOuter, this.rectInner);
    this.graphics.fillRect(p.x, p.y, 2, 2);
}

Автоматическое обновление с помощью таймера

Чтобы демонстрация была динамической, точки перерисовываются каждую секунду. Для этого используется событие таймера this.time.addEvent. Оно запускает функцию plotRandomPoints с заданной задержкой в 1000 миллисекунд (1 секунда) в бесконечном цикле.

Обратите внимание на синтаксис в исходном примере: в коллбек передается () => this.plotRandomPoints. Это стрелочная функция, которая вызывает метод. Если бы мы передали просто this.plotRandomPoints, метод вызвался бы один раз при создании события, а его результат (undefined) был бы передан как коллбек. Наш вариант — правильный.

this.time.addEvent({ delay: 1000, callback: () => this.plotRandomPoints(), loop: true });

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

В реальном проекте вместо отрисовки точек вы бы создавали игровые объекты. Например, можно генерировать врагов на уровне, но не внутри базы игрока, определенной как rectInner.

1. **Спавн врагов:** Передайте сгенерированные координаты в фабрику объектов.

const spawnPoint = Phaser.Geom.Rectangle.RandomOutside(levelBounds, safeZone);
    this.enemies.create(spawnPoint.x, spawnPoint.y, 'enemy');

2. **Разброс ресурсов:** Создавайте бонусы или ресурсы в случайных местах карты, избегая непроходимых зон. 3. **Размещение декораций:** Заполняйте фон деревьями или камнями, оставляя чистыми пути для перемещения.

Метод работает именно с прямоугольниками. Для сложных форм потребуется разбить область на несколько прямоугольников или использовать другие геометрические методы Phaser.

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

Метод RandomOutside — это отличный пример того, как встроенная геометрическая библиотека Phaser упрощает решение распространенных игровых задач. Он избавляет от необходимости писать собственные, часто громоздкие, алгоритмы проверки и генерации. Для экспериментов попробуйте: * Изменить размеры и положение внутреннего прямоугольника, чтобы увидеть, как меняется распределение точек. * Использовать полученные координаты для создания спрайтов из атласа вместо примитивов Graphics. * Реализовать спавн мобов волнами, очищая старые точки и генерируя новые с каждой волной.