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

При разработке игр с большими уровнями (платформеры, RPG, стратегии) производительность рендеринга тайловых карт становится критичной. Phaser предлагает два принципиально разных подхода: классический CPU-рендеринг и аппаратно-ускоренный GPU-рендеринг. В этой статье мы разберем, как работают оба метода на примере уровня из Super Mario, научимся переключаться между ними в реальном времени и поймем, когда какой подход выгоднее использовать для вашего проекта.

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

Живой запуск

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

Исходный код


class Example extends Phaser.Scene
{
    controls;

    preload ()
    {
        
        this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.tilemapTiledJSON('map1', 'assets/tilemaps/maps/super-mario.json');
        this.load.image('tiles1', 'assets/tilemaps/tiles/super-mario.png');
    }

    create ()
    {
        const mapCPU = this.make.tilemap({ key: 'map1' });
        const tilesetCPU = mapCPU.addTilesetImage('SuperMarioBros-World1-1', 'tiles1');
        const layerCPU = mapCPU.createLayer('World1', tilesetCPU, 0, 0).setScale(2.5);

        const mapGPU = this.make.tilemap({ key: 'map1' });
        const tilesetGPU = mapGPU.addTilesetImage('SuperMarioBros-World1-1', 'tiles1');
        const layerGPU = mapGPU.createLayer('World1', tilesetGPU, 0, 0, true).setScale(2.5).setVisible(false);

        this.mode = 'CPU';

        this.text = this.add.text(16, 16, `Click to switch to ${this.mode === 'CPU' ? 'GPU' : 'CPU'} mode.\nThis tilemap is using the ${this.mode} for rendering.`);

        const cursors = this.input.keyboard.createCursorKeys();

        const controlConfig = {
            camera: this.cameras.main,
            left: cursors.left,
            right: cursors.right,
            up: cursors.up,
            down: cursors.down,
            speed: 0.5
        };

        this.controls = new Phaser.Cameras.Controls.FixedKeyControl(controlConfig);

        this.cameras.main.setBounds(0, 0, layerCPU.x + layerCPU.width, layerCPU.height * 3);

        this.input.on('pointerdown', () => {
            if (this.mode === 'CPU')
            {
                layerCPU.setVisible(false);
                layerGPU.setVisible(true);
                this.mode = 'GPU';
            }
            else
            {
                layerCPU.setVisible(true);
                layerGPU.setVisible(false);
                this.mode = 'CPU';
            }

            this.text.setText(`Click to switch to ${this.mode === 'CPU' ? 'GPU' : 'CPU'} mode.\nThis tilemap is using the ${this.mode} for rendering.`);
        });
    }

    update (time, delta)
    {
        this.controls.update(delta);
    }
}

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

const game = new Phaser.Game(config);

Загрузка ресурсов и подготовка сцены

Основа работы с тайловыми картами в Phaser — это загрузка данных карты (в формате Tiled JSON) и графики тайлов. В методе preload() мы загружаем два ключевых ресурса.

this.load.tilemapTiledJSON('map1', 'assets/tilemaps/maps/super-mario.json');
this.load.image('tiles1', 'assets/tilemaps/tiles/super-mario.png');

После загрузки в методе create() создаются две независимые копии одной и той же карты. Обратите внимание на ключевой пятый параметр в методе createLayer().

const mapCPU = this.make.tilemap({ key: 'map1' });
const tilesetCPU = mapCPU.addTilesetImage('SuperMarioBros-World1-1', 'tiles1');
const layerCPU = mapCPU.createLayer('World1', tilesetCPU, 0, 0).setScale(2.5);

const mapGPU = this.make.tilemap({ key: 'map1' });
const tilesetGPU = mapGPU.addTilesetImage('SuperMarioBros-World1-1', 'tiles1');
const layerGPU = mapGPU.createLayer('World1', tilesetGPU, 0, 0, true).setScale(2.5).setVisible(false);

Параметр true в createLayer('World1', tilesetGPU, 0, 0, true) активирует GPU-рендеринг для этого слоя. По умолчанию (как в слое layerCPU) этот параметр равен false, что означает использование CPU.

Суть различий: CPU и GPU рендеринг

**CPU-рендеринг (Canvas):** Phaser отрисовывает каждый видимый тайл на холсте (Canvas) используя JavaScript и Canvas API. Это классический, хорошо поддерживаемый способ. Каждый кадр система перебирает видимые тайлы и рисует их как отдельные изображения. Это может создавать нагрузку на процессор при очень больших или сложных картах.

**GPU-рендеринг (WebGL):** Данные тайловой карты (позиции, индексы тайлов) передаются в видеопамять (VRAM) видеокарты. Рендерингом полностью управляет графический процессор через шейдеры. Это снимает нагрузку с CPU и значительно ускоряет отрисовку, особенно когда на экране много статичных тайлов. Однако этот режим требует поддержки WebGL и может иметь особенности с отрисовкой пиксель-арта.

В примере мы создали оба слоя сразу, но изначально виден только CPU-слой. GPU-слой скрыт методом .setVisible(false).

Управление камерой и переключение режимов

Для навигации по большой карте в примере используется система управления камерой Phaser.Cameras.Controls.FixedKeyControl. Она привязывает стрелки клавиатуры к перемещению камеры.

const controlConfig = {
    camera: this.cameras.main,
    left: cursors.left,
    right: cursors.right,
    up: cursors.up,
    down: cursors.down,
    speed: 0.5
};
this.controls = new Phaser.Cameras.Controls.FixedKeyControl(controlConfig);

Камере устанавливаются границы, соответствующие размеру карты, чтобы она не выходила за ее пределы.

this.cameras.main.setBounds(0, 0, layerCPU.x + layerCPU.width, layerCPU.height * 3);

Самое интересное — обработчик клика мыши, который переключает видимость слоев, меняя активный режим рендеринга.

this.input.on('pointerdown', () => {
    if (this.mode === 'CPU') {
        layerCPU.setVisible(false);
        layerGPU.setVisible(true);
        this.mode = 'GPU';
    } else {
        layerCPU.setVisible(true);
        layerGPU.setVisible(false);
        this.mode = 'CPU';
    }
    this.text.setText(`Click to switch to ${this.mode === 'CPU' ? 'GPU' : 'CPU'} mode.\nThis tilemap is using the ${this.mode} for rendering.`);
});

Таким образом, переключение происходит мгновенно, так как оба слоя уже созданы и находятся на своих местах.

Когда использовать CPU, а когда GPU?

Выбор режима зависит от специфики вашей игры и целевой платформы.

**Выбирайте CPU-рендеринг, если:** * Вы создаете пиксель-арт игру и хотите гарантированно четкое отображение без смазывания (фильтрация текстур в WebGL может мешать). * Целевая аудитория может использовать старые устройства или браузеры без хорошей поддержки WebGL. * Ваша тайловая карта небольшая, и разница в производительности будет незаметна.

**Выбирайте GPU-рендеринг, если:** * У вас огромные, плотные тайловые карты (открытый мир, большие подземелья). * Критична производительность и плавность FPS, особенно на мобильных устройствах. * Вы используете эффекты камеры (размытие, свечение), которые сами требуют WebGL.

В конфиге игры важно свойство pixelArt: false. Для пиксель-арта в GPU-режиме его следует установить в true, чтобы отключить линейную фильтрацию текстур и сохранить четкость.

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

Phaser дает разработчику гибкий контроль над рендерингом тайловых карт. Используйте CPU-режим для максимальной совместимости и пиксель-арта, GPU-режим — для сложных уровней, где важна производительность. Для экспериментов попробуйте

  1. создать карту с тысячами тайлов и сравнить FPS в обоих режимах
  2. включить pixelArt: true в конфиге и увидеть разницу в отрисовке
  3. добавить на сцену множество анимированных спрайтов и проверить, как меняется нагрузка при разных типах рендеринга фона