О чем этот пример
Одной из частых проблем при разработке кроссплатформенных игр на Phaser является некорректное отображение игры при изменении размера окна или ориентации устройства, особенно на Android. Пример из баг-трекера под номером 7057 наглядно демонстрирует эту проблему и предлагает её решение. В этой статье мы разберём, как использовать `Scale Manager` для создания адаптивного игрового холста, который правильно реагирует на любые изменения размеров, обеспечивая бесшовный игровой опыт на всех устройствах.
Версия Phaser: код и демо в этой статье рассчитаны на Phaser 3.90.0.
Живой запуск
Ниже встроен рабочий билд примера. Оригинальный источник: GitHub.
Исходный код
class Example extends Phaser.Scene
{
constructor()
{
super({ key: "Example" });
}
preload ()
{
this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.image("earth", "assets/pics/ayu.png");
}
create ()
{
this.earth = this.add.image(this.scale.width * 0.5, this.scale.height * 0.5, "earth");
this.text = this.add.text(10, 10, '', { font: '16px Courier', fill: '#ffffff' });
this.scale.on('resize', this.resize, this);
}
resize (gameSize, baseSize, displaySize, previousWidth, previousHeight)
{
const width = gameSize.width;
const height = gameSize.height;
this.cameras.resize(width, height);
}
update (time)
{
// cover window size
const w = this.scale.width;
const h = this.scale.height;
this.earth.setPosition(w * 0.5, h * 0.5);
this.earth.setDisplaySize(w, h);
this.text.setText([
`width: ${this.scale.width}`,
`height: ${this.scale.height}`,
`parentSize.width: ${this.scale.parentSize.width}`,
`parentSize.height: ${this.scale.parentSize.height}`,
]);
}
}
const config = {
type: Phaser.AUTO,
parent: 'phaser-example',
width: 800,
height: 600,
pixelArt: true,
scale: {
// mode: Phaser.Scale.RESIZE,
mode: Phaser.Scale.ScaleModes.RESIZE,
// autoCenter: Phaser.Scale.CENTER_BOTH
},
scene: Example
};
const game = new Phaser.Game(config);
Проблема: Исчезновение камеры при ресайзе
Исходный пример из бага 7057 показывает распространённую ошибку: при изменении размера окна игры (например, при повороте телефона) камера переставала корректно отображать игровой мир. Всё потому, что физический размер игрового холста изменился, но система камер не была об этом уведомлена.
Ключевой метод, который вызывается при изменении размера, — это resize. В исходной реализации он был пустым, что и приводило к проблеме.
Решение: Обработчик события 'resize'
Phaser предоставляет объект this.scale, который является экземпляром Scale Manager. Этот менеджер отслеживает размеры окна игры и родительского контейнера. Чтобы реагировать на изменения, нужно подписаться на его событие 'resize'.
В методе create сцены мы добавляем слушатель:
this.scale.on('resize', this.resize, this);
Это означает, что при каждом изменении размера игры будет вызван метод resize текущей сцены. Важно передать третий аргумент this, чтобы контекст (сцена) внутри метода resize был корректным.
Корректный метод resize
В обработчике события resize мы получаем несколько полезных параметров. Самый важный для нашей задачи — первый, gameSize. Он содержит новую ширину и высоту игрового холста в пикселях.
Основное действие, которое необходимо выполнить, — это сообщить системе камер о новом размере. Для этого у главной камеры сцены (или у менеджера камер) нужно вызвать метод resize.
resize (gameSize, baseSize, displaySize, previousWidth, previousHeight)
{
const width = gameSize.width;
const height = gameSize.height;
this.cameras.resize(width, height);
}
Вызов this.cameras.resize(width, height) перенастраивает все камеры сцены под новый размер области отрисовки. Без этого камера продолжит рендерить изображение в старых координатах, что приведёт к обрезанию или неправильному позиционированию игровых объектов.
Адаптация контента в реальном времени
Обработка ресайза в событии — это только половина дела. Чтобы игровой контент (например, фон) всегда заполнял весь экран, его размер и положение нужно обновлять каждый кадр. Это делается в методе update.
В примере изображение земли позиционируется по центру и растягивается на весь экран:
update (time)
{
// cover window size
const w = this.scale.width;
const h = this.scale.height;
this.earth.setPosition(w * 0.5, h * 0.5);
this.earth.setDisplaySize(w, h);
}
Здесь this.scale.width и this.scale.height возвращают текущие внутренние размеры игры. Методы setPosition и setDisplaySize объекта изображения гарантируют, что оно всегда будет на весь экран по центру, независимо от пропорций.
Конфигурация Scale Manager
Корректная работа с ресайзом начинается с правильной настройки игры в конфигурационном объекте. Ключевой является секция scale.
const config = {
// ... другие настройки ...
scale: {
mode: Phaser.Scale.ScaleModes.RESIZE,
},
scene: Example
};
Установка mode: Phaser.Scale.ScaleModes.RESIZE заставляет игровой холст динамически менять свои ширину и высоту, чтобы всегда соответствовать размеру своего родительского HTML-элемента (например, div#phaser-example). Это основной режим для создания полностью адаптивных игр, которые занимают всё отведённое им пространство.
Отладка: Отслеживание ключевых размеров
Во время разработки полезно видеть, как меняются ключевые параметры Scale Manager. В примере для этого используется текстовый объект, который обновляется в update.
this.text.setText([
`width: ${this.scale.width}`,
`height: ${this.scale.height}`,
`parentSize.width: ${this.scale.parentSize.width}`,
`parentSize.height: ${this.scale.parentSize.height}`,
]);
- `this.scale.width/height` — внутренние размеры игрового холста.
- `this.scale.parentSize.width/height` — размеры родительского HTML-элемента игры. Сравнение этих значений помогает понять, правильно ли работает режим `RESIZE` и нет ли конфликтов с CSS-стилями родительского контейнера.
Что попробовать дальше
Использование Scale Manager в режиме RESIZE вместе с корректной обработкой события 'resize' — это надёжный способ создать игру, которая бесшовно адаптируется под любой размер экрана. Для экспериментов попробуйте изменить режим масштабирования на Phaser.Scale.FIT или Phaser.Scale.ENVELOP, чтобы увидеть разное поведение. Также можно добавить обработку ориентации устройства через this.scale.orientation, чтобы по-разному отображать UI в портретной и ландшафтной ориентации на мобильных устройствах.
