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

Управление размером игрового поля во время выполнения — мощный инструмент для создания адаптивных интерфейсов, кат-сцен с разным соотношением сторон или эффектов "изменения перспективы". Часто разработчики настраивают масштаб один раз при запуске, но Phaser позволяет делать это динамически, реагируя на действия игрока. В этой статье разберем рабочий пример, который показывает, как менять размер игры по клику, переключая фоновые изображения и обновляя интерфейс. Вы научитесь использовать `ScaleManager` для живой адаптации вашего геймплея.

Версия 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('pic1', 'assets/pics/neuromancer.jpg');
        this.load.image('pic2', 'assets/pics/monika-krawinkel-amberstar-title.png');
        this.load.image('pic3', 'assets/pics/game14-angel-dawn.png');
        this.load.image('pic4', 'assets/pics/ninja-masters2.png');
    }

    create ()
    {
        const pic1 = this.add.image(0, 0, 'pic1').setOrigin(0);
        const pic2 = this.add.image(0, 0, 'pic2').setOrigin(0).setVisible(false);
        const pic3 = this.add.image(0, 0, 'pic3').setOrigin(0).setVisible(false);
        const pic4 = this.add.image(0, 0, 'pic4').setOrigin(0).setVisible(false);

        const text = this.add.text(10, 10, 'Click to change game size', { font: '16px Courier', fill: '#00ff00' });

        let state = 0;

        this.input.on('pointerdown', function ()
        {

            if (state === 0)
            {
                // this.scale.setGameSize(320, 200);
                this.scale.resize(320, 200);

                text.setText('320 x 200');

                pic1.setVisible(false);
                pic2.setVisible(true);

                state = 1;
            }
            else if (state === 1)
            {
                // this.scale.setGameSize(640, 400);
                this.scale.resize(640, 400);

                text.setText('640 x 400');

                pic2.setVisible(false);
                pic3.setVisible(true);

                state = 2;
            }
            else if (state === 2)
            {
                // this.scale.setGameSize(1216, 896);
                this.scale.resize(1216, 896);

                text.setText('1216 x 896');

                pic3.setVisible(false);
                pic4.setVisible(true);

                state = 3;
            }
            else if (state === 3)
            {
                // this.scale.setGameSize(800, 600);
                this.scale.resize(800, 600);

                text.setText('800 x 600');

                pic4.setVisible(false);
                pic1.setVisible(true);

                state = 0;
            }

        }, this);

        // this.scale.on('resize', resize, this);
    }

    resize (gameSize, baseSize, displaySize, resolution)
    {
        const width = gameSize.width;
        const height = gameSize.height;

        this.cameras.resize(width, height);
    }
}

const config = {
    type: Phaser.CANVAS,
    backgroundColor: '#2dab2d',
    scale: {
        mode: Phaser.Scale.FIT,
        parent: 'phaser-example',
        width: 800,
        height: 600
    },
    scene: Example
};

const game = new Phaser.Game(config);

Подготовка сцены и загрузка ресурсов

В методе preload() загружаются четыре фоновых изображения. Важно использовать this.load.setBaseURL() для указания базового пути, чтобы не прописывать полные URL для каждого ассета.

this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.image('pic1', 'assets/pics/neuromancer.jpg');
this.load.image('pic2', 'assets/pics/monika-krawinkel-amberstar-title.png');
this.load.image('pic3', 'assets/pics/game14-angel-dawn.png');
this.load.image('pic4', 'assets/pics/ninja-masters2.png');

В create() создаются все четыре изображения с координатами (0,0) и установленным setOrigin(0). Это выравнивает левый верхний угол картинки с левым верхним углом игрового мира. Изначально видимым оставляют только первое изображение (pic1), остальные скрывают с помощью setVisible(false). Также создается текстовый объект для отображения текущего разрешения.

const pic1 = this.add.image(0, 0, 'pic1').setOrigin(0);
const pic2 = this.add.image(0, 0, 'pic2').setOrigin(0).setVisible(false);
const pic3 = this.add.image(0, 0, 'pic3').setOrigin(0).setVisible(false);
const pic4 = this.add.image(0, 0, 'pic4').setOrigin(0).setVisible(false);
const text = this.add.text(10, 10, 'Click to change game size', { font: '16px Courier', fill: '#00ff00' });

Обработка клика и изменение размера

Логика строится вокруг переменной state, которая отслеживает текущий этап цикла (от 0 до 3). На событие pointerdown (this.input.on('pointerdown', ...)) вешается обработчик, который в зависимости от state выполняет три ключевых действия.

1. **Изменение размера игрового поля:** Вызывается метод this.scale.resize(width, height). Этот метод ScaleManager немедленно изменяет внутренний размер игры (game size). 2. **Обновление интерфейса:** Текстовому объекту задается новый текст с текущим разрешением. 3. **Смена фона:** Текущее видимое изображение скрывается, а следующее — показывается.

if (state === 0) {
    this.scale.resize(320, 200);
    text.setText('320 x 200');
    pic1.setVisible(false);
    pic2.setVisible(true);
    state = 1;
}

Обратите внимание: в старых версиях Phaser использовался метод this.scale.setGameSize(), но в актуальных (3.60+) рекомендуется использовать this.scale.resize(). В примере показана обратная совместимость — старый метод закомментирован.

Конфигурация масштабирования игры

Корректная работа динамического ресайза невозможна без правильной начальной настройки. В объект config.scale передаются важные параметры.

scale: {
    mode: Phaser.Scale.FIT,
    parent: 'phaser-example',
    width: 800,
    height: 600
}

- mode: Phaser.Scale.FIT — это самый важный параметр. Он гарантирует, что при любом изменении внутреннего размера игры (через resize), канвас будет пропорционально вписан в родительский элемент (parent), сохраняя соотношение сторон. Без этого режима изменение размера могло бы приводить к растяжению или обрезке изображения. - width и height задают стартовое разрешение игры (800x600), которое будет изменяться по клику. - parent указывает ID HTML-элемента, в который будет встроена игра.

Реакция на системное событие resize (дополнительно)

В коде примера закомментирован обработчик события resize объекта this.scale. Этот обработчик автоматически вызывается не только при ручном вызове this.scale.resize(), но и когда пользователь меняет размер окна браузера (если это разрешено настройками).

// this.scale.on('resize', resize, this);

Функция resize(gameSize, baseSize, displaySize, resolution) получает детальную информацию о новых размерах. Внутри нее можно, например, перенастроить камеры под новый размер игрового мира, чтобы видимая область соответствовала изменившимся границам.

resize (gameSize, baseSize, displaySize, resolution) {
    const width = gameSize.width;
    const height = gameSize.height;
    this.cameras.resize(width, height);
}

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

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

Метод this.scale.resize() — это прямой способ программно управлять размером игрового мира в Phaser 3. Комбинируя его с переключением видимости объектов и обработкой событий, вы можете создавать сложные адаптивные сцены и визуальные эффекты. Для экспериментов попробуйте: изменить режим масштабирования (Phaser.Scale.NONE или Phaser.Scale.ENVELOP) в конфиге и посмотреть на разное поведение; анимировать изменение размера, вызывая resize в цикле с промежуточными значениями; или привязать ресайз не к клику, а к определенным игровым событиям, например, переходу между уровнями с разной композицией.