О чем этот пример
При создании игр с большим количеством объектов производительность становится ключевым фактором. Пример из официальной коллекции Phaser демонстрирует, как физический движок Arcade использует R-Tree — структуру данных для оптимизации широкофазного обнаружения столкновений. Это позволяет системе быстро находить потенциально пересекающиеся объекты, не проверяя каждый с каждым, что критически важно для плавного геймплея.
Версия Phaser: код и демо в этой статье рассчитаны на Phaser 3.90.0.
Живой запуск
Ниже встроен рабочий билд примера. Оригинальный источник: GitHub.
Исходный код
class Example extends Phaser.Scene
{
group;
player;
cursors;
controls;
preload ()
{
this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.image('chunk', 'assets/sprites/space-baddie.png');
this.load.image('crate', 'assets/sprites/crate.png');
}
create ()
{
this.physics.world.setBounds(0, 0, 800 * 1, 600 * 1);
const bounds = new Phaser.Geom.Rectangle(30, 30, 300, 540);
this.group = this.physics.add.group({ immovable: true });
for (let i = 0; i < 50; i++)
{
const pos = bounds.getRandomPoint();
this.group.create(pos.x, pos.y, 'chunk');
}
this.cursors = this.input.keyboard.createCursorKeys();
this.player = this.physics.add.image(600, 300, 'crate');
this.physics.add.collider(this.player, this.group);
}
update ()
{
this.player.setVelocity(0);
if (this.cursors.left.isDown)
{
this.player.setVelocityX(-500);
}
else if (this.cursors.right.isDown)
{
this.player.setVelocityX(500);
}
if (this.cursors.up.isDown)
{
this.player.setVelocityY(-500);
}
else if (this.cursors.down.isDown)
{
this.player.setVelocityY(500);
}
}
}
const config = {
type: Phaser.WEBGL,
width: 800,
height: 600,
parent: 'phaser-example',
physics: {
default: 'arcade',
arcade: {
gravity: { y: 0 }
}
},
scene: Example
};
const game = new Phaser.Game(config);
Что такое R-Tree и зачем он нужен
Широкофазное обнаружение столкновений (broad-phase collision detection) — это этап, на котором система определяет, какие пары объектов находятся достаточно близко, чтобы проверять их на реальное пересечение (узкофазная проверка).
Проверка каждого объекта с каждым другим (O(n²)) крайне неэффективна при большом количестве спрайтов. R-Tree — это древовидная структура данных, которая группирует объекты в прямоугольные области (bounding boxes) и позволяет быстро отсеивать объекты, заведомо не пересекающиеся.
В Arcade Physics R-Tree используется по умолчанию для статических и неподвижных тел, что мы и видим в примере с группой (immovable: true).
Настройка сцены и создание статической группы
В методе create() происходит инициализация физического мира и создание группы статических препятствий.
this.physics.world.setBounds(0, 0, 800 * 1, 600 * 1);
const bounds = new Phaser.Geom.Rectangle(30, 30, 300, 540);
this.group = this.physics.add.group({ immovable: true });
Сначала задаются границы мира. Затем создаётся прямоугольная область bounds, внутри которой будут случайным образом размещаться спрайты. Ключевой параметр immovable: true указывает, что тела в этой группе неподвижны. Для таких тел Arcade Physics строит R-Tree, чтобы оптимизировать проверку столкновений с движущимися объектами.
for (let i = 0; i < 50; i++)
{
const pos = bounds.getRandomPoint();
this.group.create(pos.x, pos.y, 'chunk');
}
Цикл создаёт 50 спрайтов в случайных точках внутри области bounds и добавляет их в группу. Каждый спрайт автоматически получает статическое физическое тело.
Движущийся объект и регистрация коллайдера
Далее создаётся управляемый игроком объект — ящик (crate).
this.player = this.physics.add.image(600, 300, 'crate');
this.physics.add.collider(this.player, this.group);
Метод this.physics.add.image создаёт спрайт с динамическим физическим телом. Важный момент: движущийся объект (player) находится далеко от группы статиков (координата x=600 против области bounds с x от 30 до 330).
Метод this.physics.add.collider регистрирует коллайдер между игроком и группой. При проверке столкновений на каждом кадре движок, используя R-Tree для группы, быстро поймёт, что игрок находится далеко от всех статичных объектов, и пропустит ресурсоёмкие узкофазные расчёты.
Управление и обновление физики
В методе update() реализовано управление игроком с клавиатуры.
this.player.setVelocity(0);
if (this.cursors.left.isDown) { this.player.setVelocityX(-500); }
else if (this.cursors.right.isDown) { this.player.setVelocityX(500); }
if (this.cursors.up.isDown) { this.player.setVelocityY(-500); }
else if (this.cursors.down.isDown) { this.player.setVelocityY(500); }
Каждый кадр скорость объекта сначала сбрасывается, а затем назначается в зависимости от нажатых клавиш-стрелок. Физический движок Arcade автоматически применяет эту скорость, перемещает тело и проверяет столкновения.
Именно здесь вступает в работу оптимизация: когда игрок движется в пустой части экрана, R-Tree для группы статиков позволяет движку мгновенно заключить, что столкновений быть не может, не перебирая все 50 объектов.
Что попробовать дальше
Использование R-Tree в Arcade Physics — мощный, но часто незаметный инструмент для поддержания высокой частоты кадров. Он автоматически применяется к неподвижным телам (immovable: true), делая проверку столкновений эффективной.
**Идеи для экспериментов:**
1. Увеличьте количество объектов в группе до 500 и понаблюдайте за производительностью.
2. Сделайте группу динамической (уберите immovable), чтобы увидеть, как падает производительность без R-Tree.
3. Распределите статичные объекты по всей сцене и проверьте, как меняется нагрузка, когда игрок находится в их гуще.
