О чем этот пример
При создании игр часто возникает задача: объект вышел за границы экрана — что делать? Можно уничтожить его, можно заставить отскочить, а можно плавно переместить на противоположную сторону, создавая эффект бесконечного пространства. Этот подход идеально подходит для космических симуляторов, аркадных гонок или казуальных игр с парящими элементами. В статье разберем, как использовать встроенный в Phaser 3 метод `Phaser.Actions.WrapInRectangle()` для реализации такого «телепорта» объектов. Мы наглядно изучим его работу, параметры и интегрируем в игровой цикл, чтобы ваши спрайты никогда не терялись из виду.
Версия Phaser: код и демо в этой статье рассчитаны на Phaser 3.90.0.
Живой запуск
Ниже встроен рабочий билд примера. Оригинальный источник: GitHub.
Исходный код
class Example extends Phaser.Scene
{
constructor ()
{
super();
}
create ()
{
this.graphics = this.add.graphics();
this.shapes = new Array(15).fill(null).map(
() => new Phaser.Geom.Circle(Phaser.Math.Between(0, 800), Phaser.Math.Between(0, 600), Phaser.Math.Between(25, 75))
);
this.rect = Phaser.Geom.Rectangle.Clone(this.cameras.main);
}
update ()
{
this.shapes.forEach(function (shape, i) {
shape.x += (1 + 0.1 * i);
shape.y += (1 + 0.1 * i);
});
Phaser.Actions.WrapInRectangle(this.shapes, this.rect, 72);
this.draw();
}
// Locals methods, they are not part of Phaser.scene
color (i)
{
return 0x001100 * (i % 15) + 0x000033 * (i % 5);
}
draw ()
{
this.graphics.clear();
this.shapes.forEach((shape, i) => {
this.graphics
.fillStyle(this.color(i), 0.5)
.fillCircleShape(shape);
}, this);
}
}
const config = {
type: Phaser.AUTO,
width: 800,
height: 600,
parent: 'phaser-example',
scene: Example
};
const game = new Phaser.Game(config);
Суть метода WrapInRectangle
Метод Phaser.Actions.WrapInRectangle() — это мощный инструмент для управления положением массива игровых объектов. Его задача — проверить, находится ли каждый объект в пределах заданного прямоугольника (например, границ камеры). Если объект его покидает, он «перекидывается» (wrap) на противоположную сторону.
Представьте шарик, улетающий вправо за пределы экрана: он мгновенно появится слева, сохранив свою скорость и направление по оси Y. Это создает иллюзию замкнутого, циклического мира.
Phaser.Actions.WrapInRectangle(this.shapes, this.rect, 72);
В этом вызове:
- `this.shapes` — массив объектов для обработки (в нашем случае — геометрические круги).
- `this.rect` — прямоугольник (`Phaser.Geom.Rectangle`), в границах которого происходит проверка.
- `72` — необязательный радиус (padding). Метод считает, что объект имеет размер. Если координата объекта + этот радиус выходят за границу, срабатывает телепорт.
Подготовка сцены: создание объектов и границ
Вся магия начинается в методе create(). Здесь мы подготавливаем холст для рисования, массив движущихся фигур и прямоугольник-границу.
Сначала создается графический объект (Graphics) для отрисовки примитивов в каждом кадре.
this.graphics = this.add.graphics();
Затем мы создаем массив из 15 кругов (Phaser.Geom.Circle) со случайными позициями и радиусами. Это наши «игровые объекты» для демонстрации.
this.shapes = new Array(15).fill(null).map(
() => new Phaser.Geom.Circle(Phaser.Math.Between(0, 800), Phaser.Math.Between(0, 600), Phaser.Math.Between(25, 75))
);
Ключевой момент — создание прямоугольника-границы. Мы клонируем прямоугольник основной камеры (this.cameras.main). Это удобно, так как границами для обертки становятся текущие видимые границы экрана (viewport).
this.rect = Phaser.Geom.Rectangle.Clone(this.cameras.main);
Игровой цикл: движение и обертка
Логика работы метода раскрывается в update(), который выполняется каждый кадр.
На каждом кадре мы сначала двигаем все круги. Чтобы сделать движение наглядным, скорость каждого следующего круга в массиве немного увеличивается.
this.shapes.forEach(function (shape, i) {
shape.x += (1 + 0.1 * i);
shape.y += (1 + 0.1 * i);
});
После обновления позиций вызывается WrapInRectangle. Он проверяет каждый круг из массива this.shapes. Если круг (с учетом его «радиуса» в 72 пикселя) покинул пределы прямоугольника this.rect, его координаты корректируются для появления с другой стороны.
Phaser.Actions.WrapInRectangle(this.shapes, this.rect, 72);
Важно: метод модифицирует координаты (`x,y`) объектов в переданном массиве напрямую. После этой строки все круги гарантированно находятся в видимой области (или только что в нее вернулись).
Визуализация и кастомизация
После всех расчетов вызывается пользовательский метод draw() для отрисовки состояния. Он очищает холст и заново рисует все круги с уникальными цветами.
draw ()
{
this.graphics.clear();
this.shapes.forEach((shape, i) => {
this.graphics
.fillStyle(this.color(i), 0.5)
.fillCircleShape(shape);
}, this);
}
Вспомогательный метод color() генерирует цвет на основе индекса круга. Прозрачность (0.5) позволяет видеть наложения.
Практический совет: вместо радиуса в 72 пикселя вы можете использовать реальный радиус вашего спрайта или игрового объекта. Например, если у вас есть спрайт с физическим телом:
// Предположим, this.asteroids — массив спрайтов
let bounds = this.cameras.main;
Phaser.Actions.WrapInRectangle(this.asteroids, bounds, asteroid.width / 2);
Это обеспечит точную обертку по границам спрайта, а не по абстрактному значению.
Что попробовать дальше
Phaser.Actions.WrapInRectangle — это элегантное и производительное решение для создания эффекта циклического или бесконечного игрового пространства. Оно избавляет от необходимости писать велосипеды с проверками границ и расчетами переноса.
Для экспериментов попробуйте:
1. Применить метод к группе спрайтов (Phaser.GameObjects.Group) вместо массива геометрических объектов.
2. Использовать в качестве границ не прямоугольник камеры, а произвольную зону на карте, создав new Phaser.Geom.Rectangle(x, y, width, height).
3. Комбинировать обертку с другими действиями из модуля Phaser.Actions, например, с Call() для запуска анимации или звука в момент «телепорта».
