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

Векторная графика (SVG) — отличный способ добавить в игру чёткие изображения любого размера. Однако управление её масштабом напрямую влияет на производительность и удобство разработки. В Phaser есть два принципиально разных подхода: масштабирование во время загрузки (preload) и во время исполнения (run-time). Эта статья на практическом примере покажет, как и когда использовать каждый из них, чтобы вы могли делать осознанный выбор для оптимизации своих проектов.

Версия Phaser: код и демо в этой статье рассчитаны на Phaser 3.90.0.

Живой запуск

Ниже встроен рабочий билд примера. Оригинальный источник: GitHub.

Исходный код


class Example extends Phaser.Scene
{
    preload ()
    {
        this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
        this.load.svg('pencil', 'assets/svg/pencil.svg');
        this.load.svg('cartman', 'assets/svg/cartman.svg');
        this.load.svg('fireflower', 'assets/svg/fireflower.svg');

        // this.load.svg('pencil2', 'assets/svg/pencil.svg', { width: 104 * 4, height: 97 * 4 });
        // this.load.svg('cartman2', 'assets/svg/cartman.svg', { width: 104 * 4, height: 97 * 4 });

        this.load.svg('pencil2', 'assets/svg/pencil.svg', { scale: 0.2 });
        this.load.svg('cartman2', 'assets/svg/cartman.svg', { scale: 4.65 });
        this.load.svg('fireflower2', 'assets/svg/fireflower.svg', { scale: 0.3 });
    }

    create ()
    {
        const text = this.add.text(10, 10, 'Click to toggle', { font: '16px Courier', fill: '#000000' });

        //  Using default viewbox sizes + scaling:
        const svg0 = this.add.image(550, 350, 'fireflower').setScale(0.3).setOrigin(0);
        const svg1 = this.add.image(50, 100, 'cartman').setScale(3).setOrigin(0);
        const svg2 = this.add.image(440, 380, 'pencil').setScale(0.2).setOrigin(0);

        //  Using SVGs that were scaled during preload, not at run-time:
        const svg3 = this.add.image(550, 350, 'fireflower2').setOrigin(0).setVisible(false);
        const svg4 = this.add.image(50, 100, 'cartman2').setOrigin(0).setVisible(false);
        const svg5 = this.add.image(440, 380, 'pencil2').setOrigin(0).setVisible(false);

        this.input.on('pointerup', () =>
        {

            if (svg0.visible)
            {
                svg0.visible = false;
                svg1.visible = false;
                svg2.visible = false;
                svg3.visible = true;
                svg4.visible = true;
                svg5.visible = true;

                text.setText('SVGs resized during preload');
            }
            else
            {
                svg0.visible = true;
                svg1.visible = true;
                svg2.visible = true;
                svg3.visible = false;
                svg4.visible = false;
                svg5.visible = false;

                text.setText('SVGs resized at run-time');
            }

        });
    }
}

const config = {
    type: Phaser.AUTO,
    width: 800,
    height: 600,
    backgroundColor: '#efefef',
    parent: 'phaser-example',
    scene: Example
};

const game = new Phaser.Game(config);

Два пути масштабирования

Исходный код демонстрирует две техники работы с SVG. Первая — классическая: загружаем SVG-файл с исходными размерами, а затем меняем его масштаб на сцене с помощью метода setScale(). Вторая — более продвинутая: мы задаём нужный масштаб прямо в методе load.svg() на этапе прелоада. В этом случае движок создаёт текстуру (bitmap) сразу в нужном размере.

Основное различие между подходами заключается в том, *когда* происходит растеризация SVG в растровую текстуру: в первом случае — «на лету» при каждом изменении масштаба, во втором — один раз при загрузке.

Загрузка с масштабированием в preload

Ключевой метод здесь — this.load.svg(). Третий необязательный параметр — объект с настройками. В примере используется опция scale.

this.load.svg('pencil2', 'assets/svg/pencil.svg', { scale: 0.2 });
this.load.svg('cartman2', 'assets/svg/cartman.svg', { scale: 4.65 });
this.load.svg('fireflower2', 'assets/svg/fireflower.svg', { scale: 0.3 });

При такой загрузке Phaser сразу создаёт текстуру, масштабированную в указанное количество раз относительно оригинального viewBox SVG-файла. Альтернативно можно задать точные размеры в пикселях через параметры width и height (закомментированные строки в примере). Загрузка с заданным масштабом полезна для статичных изображений, размер которых известен заранее. Это снижает нагрузку на CPU во время выполнения игры.

Масштабирование в рантайме

Это более гибкий и знакомый многим подход. Сначала мы загружаем SVG в его натуральном размере.

this.load.svg('pencil', 'assets/svg/pencil.svg');

Затем, уже на сцене в методе create(), создаём изображение и применяем к нему масштабирование с помощью метода setScale().

const svg2 = this.add.image(440, 380, 'pencil').setScale(0.2).setOrigin(0);

Такой способ хорош, если объект будет динамически менять размер (например, увеличиваться при получении бонуса) или если вы хотите анимировать масштаб. Однако каждое изменение масштаба потребует перерасчёта растровой текстуры, что может сказаться на производительности при частых изменениях или большом количестве объектов.

Сравнение на практике

В примере создаётся шесть изображений: три масштабируются в рантайме (svg0, svg1, svg2), а три были масштабированы при загрузке (svg3, svg4, svg5). Изначально видна первая группа. По клику мыши происходит переключение видимости между группами.

this.input.on('pointerup', () => {
    if (svg0.visible) {
        // Скрываем рантайм-масштабированные, показываем прелоад-масштабированные
        svg0.visible = false;
        svg1.visible = false;
        svg2.visible = false;
        svg3.visible = true;
        svg4.visible = true;
        svg5.visible = true;
        text.setText('SVGs resized during preload');
    } else {
        // И наоборот
        svg0.visible = true;
        svg1.visible = true;
        svg2.visible = true;
        svg3.visible = false;
        svg4.visible = false;
        svg5.visible = false;
        text.setText('SVGs resized at run-time');
    }
});

При переключении визуальной разницы нет — оба метода дают одинаковый конечный результат на экране. Разница — в процессе рендеринга и потреблении ресурсов.

Что попробовать дальше

Выбор между масштабированием при загрузке и в рантайме — это классический компромисс между производительностью и гибкостью. Используйте прелоад-масштабирование (load.svg(key, path, { scale: X })) для статичной интерфейсной графики, фонов и объектов, размер которых не меняется. Это даст выигрыш в производительности. Масштабирование в рантайме (image.setScale(X)) оставьте для динамичных игровых объектов, пуль, эффектов и любой графики, которая должна плавно меняться в размере. Для экспериментов попробуйте загрузить один и тот же SVG с разными scale под разными ключами и сравнить потребление памяти, или создать анимацию пульсирующего размера, переключаясь между текстурами, загруженными с разным масштабом.