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

В стандартной настройке Arcade Physics все тела сталкиваются с границами игрового мира, которые совпадают с размерами сцены. Но что, если вам нужно ограничить движение объектов произвольной областью, например, экраном монитора или комнатой внутри уровня? В этой статье мы разберем, как задавать индивидуальные границы столкновений для физических тел, используя свойство `customBoundsRectangle`. Этот прием полезен для создания сложных игровых механик, где разные объекты должны взаимодействовать в своих собственных пространствах.

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

Живой запуск

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

Исходный код


// TODO rename <group custom world bounds.js>

class Example extends Phaser.Scene
{
    preload ()
    {
        this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
        this.load.image('monitor', 'assets/demoscene/monitor.png');
        this.load.image('sky', 'assets/skies/space2.png');
        this.load.spritesheet('ball', 'assets/sprites/balls.png', { frameWidth: 17, frameHeight: 17 });
    }

    create ()
    {
        this.add.image(400, 300, 'sky');

        // Balls in the default world bounds

        const balls1 = this.physics.add.group({
            key: 'ball',
            frame: 1,
            frameQuantity: 50,
            bounceX: 1,
            bounceY: 1,
            collideWorldBounds: true,
            velocityX: 100,
            velocityY: 100
        });

        Phaser.Actions.RandomRectangle(balls1.getChildren(), this.physics.world.bounds);

        this.add.image(400, 300, 'monitor');

        // Balls in smaller bounds

        const smallBounds = new Phaser.Geom.Rectangle(254, 186, 292, 210);

        const balls2 = this.physics.add.group({
            key: 'ball',
            frame: 3,
            frameQuantity: 50,
            bounceX: 1,
            bounceY: 1,
            collideWorldBounds: true,
            velocityX: 100,
            velocityY: 100
        });

        for (const ball of balls2.getChildren())
        {
            ball.body.customBoundsRectangle = smallBounds;
        }

        Phaser.Actions.RandomRectangle(balls2.getChildren(), smallBounds);
    }
}

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

const game = new Phaser.Game(config);

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

В методе preload загружаются необходимые изображения. Обратите внимание на использование this.load.setBaseURL для указания базового URL-адреса ресурсов. Это упрощает загрузку ассетов из удаленного репозитория.

preload ()
{
    this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
    this.load.image('monitor', 'assets/demoscene/monitor.png');
    this.load.image('sky', 'assets/skies/space2.png');
    this.load.spritesheet('ball', 'assets/sprites/balls.png', { frameWidth: 17, frameHeight: 17 });
}

Спрайтшит ball загружается с указанием размеров кадра, что позволяет в дальнейшем выбирать конкретные кадры для разных групп объектов.

Стандартные границы мира и первая группа шаров

В методе create сначала добавляется фон, а затем создается первая группа физических шаров с помощью this.physics.add.group. Ключевой параметр collideWorldBounds: true заставляет тела сталкиваться с границами мира, которые по умолчанию равны размерам игры (в нашем случае 800x600).

const balls1 = this.physics.add.group({
    key: 'ball',
    frame: 1,
    frameQuantity: 50,
    bounceX: 1,
    bounceY: 1,
    collideWorldBounds: true,
    velocityX: 100,
    velocityY: 100
});

Шарам задается начальная скорость и коэффициент упругости (отскока) равный 1, что означает абсолютно упругое столкновение без потери энергии. Для случайного размещения объектов используется утилита Phaser.Actions.RandomRectangle, которая распределяет детей группы внутри переданного прямоугольника — в данном случае это this.physics.world.bounds.

Phaser.Actions.RandomRectangle(balls1.getChildren(), this.physics.world.bounds);

Создание пользовательских границ

Вторая группа шаров должна двигаться внутри меньшей области, имитируя экран монитора. Сначала определяется прямоугольник smallBounds, который задает новые границы.

const smallBounds = new Phaser.Geom.Rectangle(254, 186, 292, 210);

Затем создается вторая группа шаров (balls2). Обратите внимание, что для наглядности используется другой кадр спрайтшита (frame: 3). После создания группы, для каждого физического тела в цикле устанавливается свойство body.customBoundsRectangle.

for (const ball of balls2.getChildren())
{
    ball.body.customBoundsRectangle = smallBounds;
}

Именно это свойство переопределяет глобальные границы мира для конкретного тела. Важно: параметр collideWorldBounds у группы все равно должен быть true. Изначальное размещение шаров также происходит внутри нового прямоугольника с помощью Phaser.Actions.RandomRectangle.

Phaser.Actions.RandomRectangle(balls2.getChildren(), smallBounds);

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

В конфигурационном объекте игры активируется Arcade Physics. Обратите внимание на параметр gravity: { y: 200 }. Несмотря на наличие гравитации, шары продолжают хаотично двигаться, так как им задана начальная скорость, а отскоки идеально упругие. Гравитация в данном примере влияет на траекторию, добавляя сложности движению.

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

Запуск игры выполняется стандартным образом через создание экземпляра Phaser.Game.

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

Использование customBoundsRectangle открывает возможности для создания изолированных игровых зон, окон, порталов или безопасных областей. Вы можете экспериментировать: динамически менять границы во время игры в ответ на события, создавать несколько разных зон для различных типов объектов или комбинировать эту технику с системами камеры для сложных эффектов прокрутки.