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

Статические тайлмапы в Phaser — это эффективный способ создания уровней, но их сложно анимировать как единое целое. В этой статье разберем мощный прием: рендеринг всего слоя тайлов в Render Texture. Это позволяет применять к статичной карте глобальные трансформации, например вращение, и многократно отрисовывать её в разных позициях, создавая интересные визуальные эффекты без дублирования ресурсов в памяти.

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

Живой запуск

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

Исходный код


class Example extends Phaser.Scene
{
    layer;
    rt;

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

    create ()
    {
        //  Load a map from a 2D array of tile indices
        const level = [
            [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ],
            [ 0, 1, 2, 3, 0, 0, 0, 1, 2, 3, 0 ],
            [ 0, 5, 6, 7, 0, 0, 0, 5, 6, 7, 0 ],
            [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ],
            [ 0, 0, 0, 14, 13, 14, 0, 0, 0, 0, 0 ],
            [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ],
            [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ],
            [ 0, 0, 14, 14, 14, 14, 14, 0, 0, 0, 15 ],
            [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 15 ],
            [ 35, 36, 37, 0, 0, 0, 0, 0, 15, 15, 15 ],
            [ 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39 ]
        ];

        //  When loading from an array, make sure to specify the tileWidth and tileHeight
        const map = this.make.tilemap({ data: level, tileWidth: 16, tileHeight: 16 });

        const tiles = map.addTilesetImage('mario-tiles');

        this.layer = map.createLayer(0, tiles, 0, 0).setVisible(false);

        this.rt = this.add.renderTexture(400, 300, 800, 600);
    }

    update ()
    {
        this.rt.camera.rotation -= 0.01;

        this.rt.clear();

        this.rt.draw(this.layer, 0, 0);
        this.rt.draw(this.layer, 200, 0);
        this.rt.draw(this.layer, 400, 0);
        this.rt.draw(this.layer, 0, 200);
        this.rt.draw(this.layer, 200, 200);
        this.rt.draw(this.layer, 400, 200);

        this.rt.render();
    }
}

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

const game = new Phaser.Game(config);

Создание тайлмапа из массива данных

Вместо загрузки файла карты (например, в формате JSON), мы можем создать её прямо в коде, используя двумерный массив чисел. Каждое число соответствует индексу тайла в наборе (tileset).

const level = [
    [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ],
    [ 0, 1, 2, 3, 0, 0, 0, 1, 2, 3, 0 ],
    // ... остальные строки карты
];

Метод this.make.tilemap принимает объект конфигурации. Ключевые параметры: data (наш массив level), tileWidth и tileHeight. Эти размеры необходимы, так как Phaser не может определить их автоматически из массива данных.

const map = this.make.tilemap({ data: level, tileWidth: 16, tileHeight: 16 });

Далее мы связываем загруженное изображение tileset'а с картой и создаём слой. Важный шаг — сразу скрываем слой методом .setVisible(false). Исходный слой нам не нужно отрисовывать на сцене напрямую, так как мы будем работать с его "снимком" в Render Texture.

const tiles = map.addTilesetImage('mario-tiles');
this.layer = map.createLayer(0, tiles, 0, 0).setVisible(false);

Render Texture как холст для отрисовки

Render Texture (рендер-текстура) — это особый игровой объект, который работает как динамический холст или кадровый буфер. Вы можете отрисовывать на него другие объекты (спрайты, тайлы, группы), а затем манипулировать всей полученной текстурой как единым целым.

Создаём рендер-текстуру в центре экрана размером во весь экран.

this.rt = this.add.renderTexture(400, 300, 800, 600);

Аргументы метода: координаты X и Y центра текстуры, её ширина и высота. Вся дальнейшая работа по отображению тайлмапа будет происходить через этот объект this.rt.

Магия в методе update: вращение и мультиотрисовка

Вся анимация и отрисовка выполняются каждый кадр в методе update. Ключевая идея: мы не вращаем сам слой тайлов (this.layer), а вращаем виртуальную камеру рендер-текстуры.

this.rt.camera.rotation -= 0.01;

Это приводит к тому, что всё, что будет нарисовано на this.rt в этом кадре, будет автоматически повернуто. Далее мы очищаем текстуру от содержимого предыдущего кадра.

this.rt.clear();

Теперь начинается отрисовка. Метод this.rt.draw() позволяет "штамповать" игровой объект на рендер-текстуру в указанных координатах. Мы рисуем один и тот же скрытый слой this.layer несколько раз, создавая мозаику.

this.rt.draw(this.layer, 0, 0);
this.rt.draw(this.layer, 200, 0);
this.rt.draw(this.layer, 400, 0);
// ... и так далее

Координаты (0,0) и (200,0) — это местоположение *внутри* рендер-текстуры, куда будет помещён верхний левый угол слоя. После того как все объекты отрисованы, необходимо явно вызвать финализацию.

this.rt.render();

Без вызова render() изменения на рендер-текстуре не будут отображены на экране. Этот подход очень производителен: тяжелая тайловая карта рисуется один раз, а её трансформированные и позиционированные копии создаются "дешевой" операцией отрисовки на текстуру.

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

Использование Render Texture для работы со статичными тайлмапами открывает двери для создания сложных фоновых эффектов, параллакса или "живых" карт. Для экспериментов попробуйте: анимировать не только вращение, но и масштаб камеры this.rt.camera.zoom; применять к самой рендер-текстуре пост-эффекты (например, размытие); или рисовать на неё не только тайлы, но и интерактивные спрайты, создавая динамические мини-карты.