О чем этот пример
Создание интерфейсов и игровых элементов, которые корректно масштабируются под разные экраны и контейнеры — частая задача. В Phaser для этого есть мощный, но неочевидный инструмент — класс `Phaser.Structs.Size`. Эта статья покажет, как с его помощью легко контролировать размеры одного объекта относительно другого, сохраняя нужное соотношение сторон. Вы научитесь создавать адаптивные элементы, размеры которых динамически меняются, следуя заданным правилам, что особенно полезно для UI и responsive-дизайна в играх.
Версия 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, 150, Phaser.Structs.Size.HEIGHT_CONTROLS_WIDTH, 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?
Класс Phaser.Structs.Size — это вспомогательный объект для управления шириной, высотой и соотношением сторон (aspect ratio). Его ключевая особенность — возможность установить режим, который определяет, как размеры будут изменяться относительно родительского контейнера.
Основные режимы (константы):
- Phaser.Structs.Size.NONE: Размеры меняются независимо.
- Phaser.Structs.Size.WIDTH_CONTROLS_HEIGHT: Высота вычисляется на основе ширины и заданного соотношения сторон.
- Phaser.Structs.Size.HEIGHT_CONTROLS_WIDTH: Ширина вычисляется на основе высоты и соотношения сторон.
- Phaser.Structs.Size.FIT: Объект вписывается в родительский контейнер с сохранением соотношения сторон.
- Phaser.Structs.Size.ENVELOP: Объект охватывает родительский контейнер с сохранением соотношения сторон.
В предоставленном примере используется режим HEIGHT_CONTROLS_WIDTH, что означает: ширина дочернего объекта будет автоматически пересчитываться при изменении его высоты, чтобы сохранить исходное соотношение сторон.
Разбор примера: настройка родителя и ребенка
В методе create() создаются два объекта Size: родительский (parent) и дочерний (child). Дочерний объект инициализируется с режимом, связывающим его с родителем.
const parent = new Phaser.Structs.Size(400, 300);
const child = new Phaser.Structs.Size(300, 150, Phaser.Structs.Size.HEIGHT_CONTROLS_WIDTH, parent);
Здесь parent — это фиксированный контейнер размером 400x300 пикселей. child изначально имеет размер 300x150, но его поведение определяется третьим и четвертым аргументами. Третий аргумент — режим HEIGHT_CONTROLS_WIDTH. Четвертый аргумент — ссылка на родительский объект Size. Это связывает их, хотя в данном конкретном примере родитель используется только как визуальный контейнер для отладки, а не для автоматических расчетов. Основная логика изменения размеров завязана на движение мыши.
Динамическое изменение размеров и отрисовка
Ядро примера — функция 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}`
]);
};
Обработчик события движения мыши вызывает метод setSize у дочернего объекта, передавая текущие координаты указателя.
this.input.on('pointermove', pointer =>
{
child.setSize(pointer.x, pointer.y);
draw();
});
Вот что здесь важно: несмотря на то, что в setSize передаются и pointer.x (как новая ширина), и pointer.y (как новая высота), объект child работает в режиме HEIGHT_CONTROLS_WIDTH. Это значит, что переданная высота (pointer.y) будет принята как новая высота, а ширина будет автоматически пересчитана таким образом, чтобы сохранилось исходное соотношение сторон объекта (оно вычисляется при создании как 300/150 = 2). Поэтому, перемещая мышь, вы управляете в первую очередь высотой, а ширина следует за ней по правилу: child.width = child.height * child.aspectRatio.
Практическое применение и вариации
Как это можно использовать в реальном проекте? Допустим, у вас есть панель инвентаря (родительский контейнер) и слот для предмета (дочерний элемент). Вы хотите, чтобы слот всегда был квадратным, заполняя доступную высоту панели.
// Родитель - размер панели инвентаря
const panelSize = new Phaser.Structs.Size(200, 400);
// Дочерний слот - режим HEIGHT_CONTROLS_WIDTH гарантирует квадратную форму (aspect ratio = 1)
const slotSize = new Phaser.Structs.Size(50, 50, Phaser.Structs.Size.HEIGHT_CONTROLS_WIDTH, panelSize);
// Если изменится высота панели, можно обновить слот:
slotSize.setSize(slotSize.width, newPanelHeight); // Ширина пересчитается автоматически
Для других задач попробуйте изменить режим при создании child:
- Phaser.Structs.Size.WIDTH_CONTROLS_HEIGHT: Управляйте шириной, а высота будет подстраиваться. Идеально для горизонтальных полос прогресса.
- Phaser.Structs.Size.FIT: Автоматически вписать объект в родительский контейнер. Отлично подходит для фоновых изображений, которые должны целиком помещаться в область.
Что попробовать дальше
Класс Phaser.Structs.Size предоставляет элегантный способ управления размерами с соблюдением пропорций. Он избавляет от ручных вычислений при изменении размеров игровых окон, UI-элементов или областей отрисовки. Для экспериментов попробуйте
- Привязать размеры
childне к курсору, а к реальному игровому объекту (спрайту) - Использовать режим
FITдля автоматического масштабирования картинки под динамически меняющуюся панель - Создать цепочку из нескольких связанных объектов
Size, где изменение одного каскадно влияет на другие
