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

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

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

Живой запуск

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

Исходный код


class SceneA extends Phaser.Scene
{
    constructor ()
    {
        super({ key: 'sceneA' });
    }

    preload ()
    {
        this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
        this.load.image('ball1', 'assets/sprites/pangball.png');
    }

    create ()
    {
        this.matter.world.setBounds(0, 0, 800, 600, 32, true, true, false, true);

        for (let i = 0; i < 64; i++)
        {
            const ball = this.matter.add.image(Phaser.Math.Between(100, 700), Phaser.Math.Between(-600, 0), 'ball1');
            ball.setCircle();
            ball.setFriction(0.005);
            ball.setBounce(1);
        }

        this.input.once('pointerdown', function (event)
        {

            this.scene.start('sceneB');

        }, this);
    }
}

class SceneB extends Phaser.Scene
{
    constructor ()
    {
        super({ key: 'sceneB' });
    }

    preload ()
    {
        // this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
        this.load.image('ball2', 'assets/sprites/shinyball.png');
    }

    create ()
    {
        this.matter.world.setBounds(0, 0, 800, 600, 32, true, true, false, true);

        for (let i = 0; i < 64; i++)
        {
            const ball = this.matter.add.image(Phaser.Math.Between(100, 700), Phaser.Math.Between(-600, 0), 'ball2');
            ball.setCircle();
            ball.setFriction(0.005);
            ball.setBounce(1);
        }

        this.input.once('pointerdown', function (event)
        {

            this.scene.start('sceneA');

        }, this);
    }
}

const config = {
    type: Phaser.WEBGL,
    width: 800,
    height: 600,
    backgroundColor: '#000000',
    parent: 'phaser-example',
    scene: [ SceneA, SceneB ],
    physics: {
        default: 'matter'
    }
};

const game = new Phaser.Game(config);

Структура проекта и конфигурация

Основная конфигурация игры задается один раз. Ключевой параметр — physics.default, который устанавливает Matter.js в качестве движка физики для всех сцен по умолчанию. Сцены передаются в массив scene в том порядке, в котором они будут созданы, но первая активная сцена определяется вызовом this.scene.start().

const config = {
    type: Phaser.WEBGL,
    width: 800,
    height: 600,
    backgroundColor: '#000000',
    parent: 'phaser-example',
    scene: [ SceneA, SceneB ], // Массив классов сцен
    physics: {
        default: 'matter' // Глобальная настройка физики
    }
};

const game = new Phaser.Game(config);

Класс сцены и загрузка ресурсов

Каждая сцена — это отдельный класс, расширяющий Phaser.Scene. В конструкторе задается уникальный ключ сцены, который используется для переключения. Метод preload отвечает за загрузку ресурсов, специфичных для данной сцены. Важно понимать, что загрузка происходит только один раз при первом создании сцены. В примере SceneA и SceneB загружают разные изображения для шаров.

class SceneA extends Phaser.Scene {
    constructor () {
        super({ key: 'sceneA' }); // Уникальный ключ
    }

    preload () {
        this.load.setBaseURL('...');
        this.load.image('ball1', 'assets/sprites/pangball.png');
    }
}

Создание физического мира и объектов

Метод create вызывается, когда сцена становится активной. Здесь настраивается физический мир через this.matter.world.setBounds(), задаются границы и их видимость. Затем создаются физические тела — в данном случае, 64 шара, которые падают с верхней части экрана. Каждый шар настраивается как круг с помощью setCircle(), ему задаются параметры трения и упругости.

create () {
    // Установка границ мира: x, y, width, height, thickness,
    // left, right, top, bottom (булевы значения)
    this.matter.world.setBounds(0, 0, 800, 600, 32, true, true, false, true);

    for (let i = 0; i < 64; i++) {
        const ball = this.matter.add.image(
            Phaser.Math.Between(100, 700),
            Phaser.Math.Between(-600, 0),
            'ball1'
        );
        ball.setCircle(); // Делаем тело круглым
        ball.setFriction(0.005); // Очень низкое трение
        ball.setBounce(1); // Абсолютно упругий удар
    }
}

Обратите внимание: this.matter.add.image создает не просто спрайт, а физическое тело с текстурой.

Механизм переключения сцен

Переключение между сценами инициируется по событию (в примере — по клику). Метод this.scene.start() останавливает текущую сцену, запускает целевую и вызывает ее методы init, preload (если ресурсы еще не загружены), create. При этом предыдущая сцена и все ее объекты уничтожаются, освобождая память. Matter.js мир текущей сцены также останавливается.

this.input.once('pointerdown', function (event) {
    this.scene.start('sceneB'); // Запуск сцены по ключу
}, this);

Использование once гарантирует, что обработчик сработает только один раз, предотвращая множественные переключения.

Изоляция и производительность

Каждая сцена управляет своим собственным экземпляром мира Matter.js. Когда вы переключаетесь с SceneA на SceneB, мир SceneA полностью останавливается и удаляется сборщиком мусора (если на него нет ссылок), а мир SceneB инициализируется заново. Это обеспечивает полную изоляцию: шары из SceneA не влияют на SceneB. Для оптимизации:

- Загружайте ресурсы в preload заранее. - Убедитесь, что в предыдущей сцене не осталось активных слушателей событий или таймеров (используйте this.scene.stop() или методы жизненного цикла). - Используйте общие ресурсы через кэш загрузчика Phaser, чтобы не грузить одну текстуру дважды.

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

Переключение сцен с независимыми мирами Matter.js — мощный паттерн для организации кода в Phaser. Он позволяет создавать сложные игры с четким разделением состояний. Для экспериментов попробуйте

  1. передавать данные между сценами через this.scene.start(key, data)
  2. использовать несколько активных сцен одновременно (this.scene.launch())
  3. сохранять состояние мира при переключении, не уничтожая его, или
  4. создать сцену-меню без физики вообще