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

Создание интерфейсов и игровых элементов, которые корректно масштабируются под разные экраны и контейнеры — частая задача. В 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-элементов или областей отрисовки. Для экспериментов попробуйте

  1. Привязать размеры child не к курсору, а к реальному игровому объекту (спрайту)
  2. Использовать режим FIT для автоматического масштабирования картинки под динамически меняющуюся панель
  3. Создать цепочку из нескольких связанных объектов Size, где изменение одного каскадно влияет на другие