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

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

Версия 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('rain', 'assets/pics/thalion-rain.png');
        this.load.image('logo', 'assets/sprites/phaser3-logo-x2.png');
    }

    create ()
    {
        this.bg = this.add.tileSprite(0, 0, this.scale.width, this.scale.height, 'rain').setOrigin(0);
        this.logo = this.add.sprite(this.scale.width / 2, this.scale.height / 2, 'logo');

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

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

        this.cameras.resize(width, height);

        this.bg.setSize(width, height);
        this.logo.setPosition(width / 2, height / 2);
    }
}

const config = {
    type: Phaser.AUTO,
    backgroundColor: '#2dab2d',
    scale: {
        mode: Phaser.Scale.RESIZE,
        parent: 'phaser-example',
        width: '100%',
        height: '100%'
    },
    scene: Example
};

const game = new Phaser.Game(config);

Настройка менеджера масштабирования (Scale Manager)

Ключ к адаптивности в Phaser — правильная конфигурация объекта scale в конфигурации игры. В примере используется режим Phaser.Scale.RESIZE, который заставляет игровое полотно (canvas) динамически менять свои физические размеры в пикселях, подстраиваясь под размеры своего контейнера (parent).

const config = {
    type: Phaser.AUTO,
    backgroundColor: '#2dab2d',
    scale: {
        mode: Phaser.Scale.RESIZE,
        parent: 'phaser-example',
        width: '100%',
        height: '100%'
    },
    scene: Example
};

Указание ширины и высоты в процентах означает, что игра будет стремиться занять 100% ширины и высоты контейнера с id='phaser-example'. Когда размеры этого HTML-элемента меняются (например, пользователь меняет размер окна браузера), менеджер масштабирования генерирует событие resize.

Подписка на событие изменения размера

Чтобы реагировать на изменение размеров, нужно подписаться на событие resize, которое генерирует объект this.scale. Лучше всего это делать в методе create сцены, после того как все основные игровые объекты созданы.

create ()
{
    this.bg = this.add.tileSprite(0, 0, this.scale.width, this.scale.height, 'rain').setOrigin(0);
    this.logo = this.add.sprite(this.scale.width / 2, this.scale.height / 2, 'logo');

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

Метод this.scale.on принимает три аргумента: имя события, функцию-обработчик (this.resize) и контекст, в котором она будет вызвана (this — текущая сцена). Важно передать контекст, иначе внутри функции resize this будет указывать не на сцену, а на другой объект, и мы не сможем получить доступ к this.bg или this.cameras.

Обработчик события и обновление игрового мира

Функция-обработчик resize получает несколько параметров, но для базовой адаптации чаще всего нужен первый — gameSize. Он содержит актуальную ширину и высоту игрового полотна в пикселях.

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

    this.cameras.resize(width, height);

    this.bg.setSize(width, height);
    this.logo.setPosition(width / 2, height / 2);
}

Первым делом необходимо перенастроить основную камеру сцены под новые размеры с помощью this.cameras.resize. Если этого не сделать, область просмотра камеры может не совпадать с размерами canvas, что приведет к обрезке изображения или появлению черных полей. Затем обновляются ключевые игровые объекты: фоновый TileSprite растягивается на всю новую площадь, а спрайт логотипа центрируется.

Важные нюансы и производительность

Событие resize может срабатывать очень часто (например, при плавном изменении размера окна). Поэтому обработчик должен выполнять только необходимые минимальные операции. Избегайте в нем загрузки ресурсов, создания сложной логики или массового пересчета физических тел.

Инициализируйте объекты, используя this.scale.width и this.scale.height, как показано в create. Это гарантирует, что их начальная позиция и размер будут корректны при первом запуске, до любого события resize.

// Хорошо: используем актуальные размеры при создании
this.logo = this.add.sprite(this.scale.width / 2, this.scale.height / 2, 'logo');

// Плохо: используем жестко заданные значения
this.logo = this.add.sprite(400, 300, 'logo'); // Может оказаться не в центре на другом разрешении

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

Использование Phaser.Scale.RESIZE и обработка одноименного события — основа создания игр, которые бесшовно адаптируются к любому размеру экрана. Для экспериментов попробуйте

  1. Добавить больше UI-элементов (кнопки, панели здоровья) и реализовать их перепозиционирование в resize
  2. Создать сложную композицию из нескольких слоев (Container) и масштабировать весь контейнер
  3. Реализовать условную логику, которая при определенном соотношении сторон (width/height) меняет расположение элементов интерфейса (портретная/ландшафтная ориентация)