О чем этот пример
При разработке игр часто возникает задача вписать один элемент в границы другого, сохранив пропорции. Ручные расчёты соотношения сторон и позиционирования отнимают время и генерируют лишний код. Класс `Phaser.Structs.Size` предоставляет элегантное решение для декларативного описания размеров и их взаимосвязей. В этой статье разберем, как использовать режим `FIT` для автоматического масштабирования одного размера относительно другого, что особенно полезно для интерфейсов, камер и адаптивных игровых объектов.
Версия Phaser: код и демо в этой статье рассчитаны на Phaser 3.90.0.
Живой запуск
Ниже встроен рабочий билд примера. Оригинальный источник: GitHub.
Исходный код
class Example extends Phaser.Scene
{
create ()
{
const debug = this.add.graphics();
const text = this.add.text(10, 540, '', { fill: '#00ff00' });
const parent = new Phaser.Structs.Size(400, 300);
const child = new Phaser.Structs.Size(320, 190, Phaser.Structs.Size.FIT, parent);
const draw = () =>
{
debug.clear().translateCanvas(10, 10);
debug.lineStyle(1.5, 0xffff00).strokeRect(1, 1, parent.width, parent.height);
debug.fillStyle(0x00ff00, 0.5).fillRect(1, 1, child.width, child.height);
text.setText([
`width: ${child.width}`,
`height: ${child.height}`,
`aspect ratio: ${child.aspectRatio}`
]);
};
this.input.on('pointermove', pointer =>
{
child.setSize(pointer.x, pointer.y);
draw();
});
draw();
}
}
const config = {
type: Phaser.AUTO,
parent: 'phaser-example',
width: 800,
height: 600,
scene: Example
};
const game = new Phaser.Game(config);
Создание и связывание размеров
Класс Phaser.Structs.Size представляет собой контейнер для ширины, высоты и правил их изменения. В примере создаются два объекта: родительский и дочерний размер.
const parent = new Phaser.Structs.Size(400, 300);
const child = new Phaser.Structs.Size(320, 190, Phaser.Structs.Size.FIT, parent);
Первый аргумент конструктора — ширина, второй — высота. Для дочернего размера ключевыми являются третий и четвёртый параметры. Phaser.Structs.Size.FIT — это режим, который заставляет дочерний размер "вписаться" в границы родительского, сохраняя свои исходные пропорции (aspect ratio). Последний аргумент parent указывает, относительно какого размера происходит масштабирование. После создания объект child автоматически пересчитывает свои width и height, чтобы поместиться внутри parent, не вылезая за его границы.
Визуализация и обновление в реальном времени
Чтобы увидеть результат, в сцене создаётся графика для отладки (debug) и текстовое поле (text). Функция draw очищает холст, рисует рамку родительского размера и залитый прямоугольник дочернего.
const draw = () => {
debug.clear().translateCanvas(10, 10);
debug.lineStyle(1.5, 0xffff00).strokeRect(1, 1, parent.width, parent.height);
debug.fillStyle(0x00ff00, 0.5).fillRect(1, 1, child.width, child.height);
text.setText([
`width: ${child.width}`,
`height: ${child.height}`,
`aspect ratio: ${child.aspectRatio}`
]);
};
Метод translateCanvas смещает начало координат отрисовки, чтобы визуализация не накладывалась на текст. Важно: мы рисуем прямоугольники, используя актуальные свойства child.width и child.height, которые уже содержат скорректированные под режим FIT значения.
Интерактивное изменение и пересчёт
Механика демонстрации завязана на движении указателя. При событии pointermove мы меняем исходный (желаемый) размер дочернего элемента и вызываем перерисовку.
this.input.on('pointermove', pointer => {
child.setSize(pointer.x, pointer.y);
draw();
});
Метод setSize обновляет внутренние цели дочернего размера. После этого вызова объект child автоматически применяет правило FIT относительно своего родителя parent. Если новые переданные ширину и высоту невозможно одновременно вписать в родительский контейнер с сохранением пропорций, будет выбран максимально возможный вариант. Например, если указать очень широкий размер, то ограничивающим фактором станет ширина родителя (400), а высота будет вычислена пропорционально.
Практическое применение в играх
Хотя пример рисует простые прямоугольники, технология полезна для реальных задач.
* **Адаптивный UI**: Создайте Size для контейнера интерфейса (например, панели навыков) и Size в режиме FIT для иконки внутри неё. Иконка всегда будет помещаться в панель, не искажаясь.
* **Динамическая камера**: Определите Size для игровой области и Size в режиме FIT для области просмотра камеры. Это поможет автоматически рассчитывать zoom камеры, чтобы игровой уровень всегда был виден целиком.
* **Обработка ресайза окна**: При событии изменения размера окна браузера обновите родительский Size (например, установите в него новые размеры игрового канваса). Все связанные дочерние размеры в режиме FIT пересчитаются автоматически.
// Пример: создание адаптивной текстуры для контейнера
const uiContainerSize = new Phaser.Structs.Size(300, 150);
const iconSize = new Phaser.Structs.Size(200, 100, Phaser.Structs.Size.FIT, uiContainerSize);
// Создаём спрайт с вычисленными размерами
this.add.sprite(x, y, 'icon').setDisplaySize(iconSize.width, iconSize.height);
Что попробовать дальше
Класс Phaser.Structs.Size с режимом FIT избавляет от рутинной математики при масштабировании, обеспечивая чистый и поддерживаемый код. Для экспериментов попробуйте заменить режим на Phaser.Structs.Size.ENVELOP (когда элемент должен полностью покрыть родительскую область) или привяжите изменение родительского размера к жесту pinch-zoom для создания интерактивного редактора макетов.
