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

При разработке игр с тайловыми картами часто возникает необходимость комбинировать различные наборы спрайтов для создания детализированных и разнообразных уровней. В этой статье мы разберем, как загружать и использовать несколько тайлсетов в одном слое карты в Phaser, что является ключевым навыком для создания визуально насыщенных игровых миров. Вы научитесь подключать несколько графических наборов, загруженных из редактора Tiled, и управлять камерой для навигации по большой карте.

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

Живой запуск

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

Исходный код


class Example extends Phaser.Scene
{
    constructor()
    {
        super();
    }

    preload()
    {
        this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
        this.load.image('ground', 'assets/tilemaps/tiles/kenny_ground_64x64.png');
        this.load.image('items', 'assets/tilemaps/tiles/kenny_items_64x64.png');
        this.load.image('platformer', 'assets/tilemaps/tiles/kenny_platformer_64x64.png');
        this.load.tilemapTiledJSON('map', 'assets/tilemaps/maps/multi-tileset.json');
    }

    create()
    {
        var map = this.make.tilemap({ key: 'map' });

        var groundTiles = map.addTilesetImage('kenny_ground_64x64', 'ground');
        var itemTiles = map.addTilesetImage('kenny_items_64x64', 'items');
        var platformTiles = map.addTilesetImage('kenny_platformer_64x64', 'platformer');

        //  To use multiple tilesets in a single layer, pass them in an array like this:
        map.createLayer('Tile Layer 1', [ groundTiles, itemTiles, platformTiles ]);

        //  Or you can pass an array of strings, where they = the Tileset name
        // map.createLayer('Tile Layer 1', [ 'kenny_ground_64x64', 'kenny_items_64x64', 'kenny_platformer_64x64' ]);

        this.cameras.main.setBounds(0, 0, map.widthInPixels, map.heightInPixels);

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

        var 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);

        var help = this.add.text(16, 16, 'Arrow keys to scroll', {
            fontSize: '18px',
            padding: { x: 10, y: 5 },
            backgroundColor: '#000000',
            fill: '#ffffff'
        });

        help.setScrollFactor(0);
    }

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

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

const game = new Phaser.Game(config);

Загрузка ресурсов: карта и тайлсеты

В методе preload() происходит загрузка всех необходимых ресурсов. Важно загрузить как саму карту в формате JSON, экспортированную из Tiled, так и все изображения тайлсетов, которые на ней используются. Обратите внимание, что метод load.setBaseURL() устанавливает базовый URL для всех последующих загрузок, что позволяет указывать только относительные пути.

preload()
{
    this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
    this.load.image('ground', 'assets/tilemaps/tiles/kenny_ground_64x64.png');
    this.load.image('items', 'assets/tilemaps/tiles/kenny_items_64x64.png');
    this.load.image('platformer', 'assets/tilemaps/tiles/kenny_platformer_64x64.png');
    this.load.tilemapTiledJSON('map', 'assets/tilemaps/maps/multi-tileset.json');
}

Создание тайловой карты и связывание тайлсетов

В методе create() мы создаем объект тайловой карты с помощью this.make.tilemap(). Ключевой этап — это связывание загруженных в память изображений с именами тайлсетов, которые были заданы в редакторе Tiled. Для этого используется метод map.addTilesetImage(). Первым аргументом передается имя тайлсета из файла карты (Tiled), вторым — ключ загруженного изображения в Phaser.

create()
{
    var map = this.make.tilemap({ key: 'map' });

    var groundTiles = map.addTilesetImage('kenny_ground_64x64', 'ground');
    var itemTiles = map.addTilesetImage('kenny_items_64x64', 'items');
    var platformTiles = map.addTilesetImage('kenny_platformer_64x64', 'platformer');
}

В результате groundTiles, itemTiles и platformTiles становятся объектами, которые Phaser может использовать для отрисовки соответствующей части карты.

Создание слоя с несколькими тайлсетами

Phaser позволяет использовать несколько тайлсетов для одного слоя карты. Это особенно удобно, когда вы хотите создать сложный слой, состоящий из тайлов разных тематик (например, земля, предметы и платформы). Для этого методу map.createLayer() вторым аргументом нужно передать массив объектов тайлсетов, созданных на предыдущем шаге.

//  To use multiple tilesets in a single layer, pass them in an array like this:
    map.createLayer('Tile Layer 1', [ groundTiles, itemTiles, platformTiles ]);

Альтернативный способ — передать массив строк с именами тайлсетов (как они указаны в Tiled). Phaser самостоятельно найдет ранее созданные связанные объекты.

//  Or you can pass an array of strings, where they = the Tileset name
    // map.createLayer('Tile Layer 1', [ 'kenny_ground_64x64', 'kenny_items_64x64', 'kenny_platformer_64x64' ]);

Настройка камеры и элементов управления

Поскольку тайловая карта может быть больше, чем игровое окно, необходимо настроить камеру и управление для ее прокрутки. Метод this.cameras.main.setBounds() устанавливает границы, за которые камера не может выйти, совпадающие с размерами карты в пикселях.

this.cameras.main.setBounds(0, 0, map.widthInPixels, map.heightInPixels);

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

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

    var 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);

Также создается текстовый элемент с подсказкой. Важно вызвать для него help.setScrollFactor(0), чтобы текст оставался фиксированным на экране и не двигался вместе с камерой.

Обновление состояния управления

Для того чтобы управление камерой работало, необходимо в каждом кадре обновлять состояние контролов. Это делается в методе update(), который автоматически вызывается игровым циклом Phaser.

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

Метод this.controls.update(delta) принимает параметр delta (время, прошедшее с предыдущего кадра), что позволяет обеспечить плавное и независимое от частоты кадров движение камеры.

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

Использование нескольких тайлсетов в Phaser открывает большие возможности для дизайна уровней, позволяя комбинировать различные графические наборы без создания огромных спрайтшитов. Для экспериментов попробуйте создать в Tiled карту, использующую 4-5 различных тайлсетов, и загрузите ее в Phaser. Исследуйте, как работает отрисовка слоев, если тайлсеты в массиве переданы в разном порядке, или добавьте несколько разных слоев (фон, объекты, декорации), каждый со своим уникальным набором тайлсетов.