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

Создание динамичных игровых миров требует, чтобы объекты сталкивались друг с другом по-разному. В этом примере мы покажем, как один движущийся спрайт может взаимодействовать с двумя независимыми группами физических тел — шарами и ящиками — используя единый обработчик столкновений. Это фундаментальный паттерн для игр с множеством типов препятствий, платформ или врагов, где нужно эффективно управлять физикой.

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

Живой запуск

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

Исходный код


class Example extends Phaser.Scene
{
    preload ()
    {
        this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
        this.load.image('mushroom', 'assets/sprites/mushroom2.png');
        this.load.image('ball', 'assets/sprites/shinyball.png');
        this.load.image('crate', 'assets/sprites/crate32.png');
    }

    create ()
    {
        const sprite = this.physics.add.image(400, 300, 'mushroom');

        const outer = new Phaser.Geom.Rectangle(0, 0, 800, 600);
        const inner = new Phaser.Geom.Rectangle(350, 250, 100, 100);

        //  Create a few balls

        const balls = this.physics.add.group({ immovable: true });

        for (let i = 0; i < 8; i++)
        {
            const point = Phaser.Geom.Rectangle.RandomOutside(outer, inner);
            const ball = balls.create(point.x, point.y, 'ball');

            this.physics.add.existing(ball);

            ball.body.setImmovable();
        }

        //  Create a few crates

        const crates = this.physics.add.group({ immovable: true });

        for (let i = 0; i < 8; i++)
        {
            const point = Phaser.Geom.Rectangle.RandomOutside(outer, inner);
            const ball = crates.create(point.x, point.y, 'crate');

            this.physics.add.existing(ball);

            ball.body.setImmovable();
        }

        sprite.setVelocity(100, 200).setBounce(1, 1).setCollideWorldBounds(true).setGravityY(200);

        this.physics.add.collider(sprite, [ balls, crates ]);
    }
}

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

const game = new Phaser.Game(config);

Настройка сцены и загрузка ресурсов

Класс Example расширяет Phaser.Scene. В методе preload мы задаем базовый URL для загрузки и загружаем три текстуры: гриб, блестящий шар и ящик. Эти ассеты будут использоваться как спрайты.

this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.image('mushroom', 'assets/sprites/mushroom2.png');
this.load.image('ball', 'assets/sprites/shinyball.png');
this.load.image('crate', 'assets/sprites/crate32.png');

Создание главного спрайта и определение зон

В методе create мы создаем главный физический спрайт — гриб — с помощью this.physics.add.image. Затем определяем две геометрические области: outer (весь экран 800x600) и inner (центральный прямоугольник 100x100). Область inner будет исключена из зоны появления объектов.

const sprite = this.physics.add.image(400, 300, 'mushroom');

const outer = new Phaser.Geom.Rectangle(0, 0, 800, 600);
const inner = new Phaser.Geom.Rectangle(350, 250, 100, 100);

Создание первой группы объектов — шаров

Создаем первую физическую группу balls. Ключевой параметр { immovable: true } означает, что тела в группе по умолчанию будут неподвижными при столкновениях. В цикле мы генерируем 8 случайных точек за пределами центрального прямоугольника с помощью Phaser.Geom.Rectangle.RandomOutside. Для каждого созданного спрайта в группе нужно явно добавить физическое тело через this.physics.add.existing и установить его как неподвижное.

const balls = this.physics.add.group({ immovable: true });

for (let i = 0; i < 8; i++)
{
    const point = Phaser.Geom.Rectangle.RandomOutside(outer, inner);
    const ball = balls.create(point.x, point.y, 'ball');

    this.physics.add.existing(ball);
    ball.body.setImmovable();
}

Создание второй группы объектов — ящиков

Аналогичным образом создаем вторую группу crates. Важно: хотя группа создана с параметром immovable: true, для каждого конкретного спрайта всё равно требуется явно вызвать this.physics.add.existing и setImmovable(), чтобы тело было корректно инициализировано в Arcade Physics. Это обеспечивает консистентность.

const crates = this.physics.add.group({ immovable: true });

for (let i = 0; i < 8; i++)
{
    const point = Phaser.Geom.Rectangle.RandomOutside(outer, inner);
    const ball = crates.create(point.x, point.y, 'crate');

    this.physics.add.existing(ball);
    ball.body.setImmovable();
}

Настройка физики главного спрайта и коллайдера

Задаем грибу начальную скорость, упругость (setBounce(1,1) для идеального отскока), включаем столкновение с границами мира и добавляем вертикальную гравитацию. Самое важное — создание коллайдера через this.physics.add.collider. В качестве второго аргумента мы передаем массив из двух групп [ balls, crates ]. Это заставляет гриб сталкиваться одновременно с объектами из обеих групп, используя один вызов.

sprite.setVelocity(100, 200).setBounce(1, 1).setCollideWorldBounds(true).setGravityY(200);

this.physics.add.collider(sprite, [ balls, crates ]);

Конфигурация игры и запуск

В конфигурации игры активируем Arcade Physics с опцией debug: true, чтобы видеть хитбоксы. Созданный экземпляр Phaser.Game запускает нашу сцену.

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

const game = new Phaser.Game(config);

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

Использование массива групп в collider — это мощный и производительный способ организовать сложные взаимодействия в игре. Вы можете расширить этот пример, добавив группам разные свойства (например, трение или отскок) или создав коллайдеры между самими группами. Попробуйте изменить логику появления объектов или добавить третью группу с врагами, которые тоже будут сталкиваться с игроком.