О чем этот пример
При разработке игр часто возникает задача подгонки размеров одного объекта под границы другого с сохранением пропорций. Например, вы хотите, чтобы изображение или UI-элемент всегда помещалось внутри заданной области, не вылезая за её края. Вручную рассчитывать такие размеры — утомительно и легко ошибиться. Класс `Phaser.Structs.Size` в Phaser 3 предлагает встроенное решение — режим `ENVELOP`. Он автоматически масштабирует дочерний объект так, чтобы он полностью помещался внутри родительского, сохраняя свои исходные пропорции (aspect ratio). Эта статья покажет, как использовать этот инструмент на практическом примере.
Версия 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(300, 190, Phaser.Structs.Size.ENVELOP, 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. Этот класс — не игровой объект, а структура данных, хранящая информацию о ширине, высоте и правилах их изменения.
Сначала создаём родительский размер — фиксированную область-контейнер. Затем создаём дочерний размер, который будет меняться. Ключевой момент — третий аргумент при создании child: константа Phaser.Structs.Size.ENVELOP. Именно она включает нужный нам режим подгонки. Четвёртый аргумент — ссылка на родительский объект, который будет задавать границы.
const parent = new Phaser.Structs.Size(400, 300);
const child = new Phaser.Structs.Size(300, 190, Phaser.Structs.Size.ENVELOP, parent);
Визуализация и отладка
Чтобы увидеть работу алгоритма, мы нарисуем оба прямоугольника. Жёлтым контуром обозначается родительская область, а полупрозрачным зелёным прямоугольником — дочерняя, размеры которой рассчитывает ENVELOP.
Функция draw() очищает холст, рисует оба прямоугольника и выводит в текстовое поле актуальные размеры и соотношение сторон (aspect ratio) дочернего объекта. Это позволяет сразу видеть результат вычислений.
const debug = this.add.graphics();
const text = this.add.text(10, 540, '', { fill: '#00ff00' });
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}`
]);
};
Динамическое взаимодействие
Суть демонстрации — показать, как дочерний размер реагирует на изменения. Мы привязываемся к событию перемещения указателя pointermove. Координаты курсора (pointer.x, pointer.y) передаются как желаемые ширина и высота для child.
Вот магия ENVELOP: даже если мы запрашиваем размеры, которые не вписываются в родительские границы (например, слишком широкий прямоугольник), объект child сам пересчитает свои width и height. Он выберет такой максимальный размер, при котором сохранится исходное соотношение сторон *и* он целиком поместится внутрь parent.
this.input.on('pointermove', pointer => {
child.setSize(pointer.x, pointer.y);
draw();
});
Как работает алгоритм ENVELOP?
Когда вы вызываете setSize() для объекта в режиме ENVELOP, происходит не прямое присвоение значений. Вместо этого Phaser вычисляет масштаб для двух сценариев:
1. Подгон по ширине: если бы ширину сделали равной ширине родителя, на какую высоту нужно измениться, чтобы сохранить пропорции?
2. Подгон по высоте: если бы высоту сделали равной высоте родителя, какой станет ширина?
Алгоритм выбирает тот вариант масштаба, который даёт *меньший* результирующий размер. Это гарантирует, что дочерний прямоугольник не превысит границы родителя ни по одной из осей. Исходное соотношение сторон (aspectRatio) объекта child при этом остаётся неизменным.
Что попробовать дальше
Режим Phaser.Structs.Size.ENVELOP — это мощный и малоизвестный инструмент для расчёта вложенных размеров. Он избавляет от ручных вычислений с Math.min и проверками пропорций.
**Идеи для экспериментов:**
1. Используйте этот подход для автоматического масштабирования фоновых изображений под разное разрешение экрана.
2. Примените ENVELOP для динамических UI-окон, которые должны помещаться внутри игрового viewport.
3. Создайте систему предпросмотра предметов в инвентаре, где иконка всегда помещается в заданную ячейку, не искажаясь.
