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

Создание сложных игровых локаций часто требует комбинации нескольких наборов тайлов (tilesets). Например, отдельно земля, предметы и элементы платформера. 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()`

На этапе предзагрузки важно подготовить все изображения для тайлов и сам файл карты. Ключевой метод — load.tilemapTiledJSON. Он загружает файл карты, созданный в редакторе Tiled, который содержит информацию о слоях и ссылки на используемые tilesets по их именам.

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');
}

Каждому изображению присваивается ключ ('ground', 'items', 'platformer'), по которому мы будем к нему обращаться. Файл карты 'map' содержит структуру уровня и имена тайлсетов, которые использует (например, 'kenny_ground_64x64').

Создание карты и связывание Tilesets в `create()`

После загрузки создаем объект тайловой карты и вручную связываем имена тайлсетов из файла 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');
}

Метод map.addTilesetImage выполняет связывание. Первый аргумент — это имя тайлсета, **как оно указано в файле Tiled JSON** ('kenny_ground_64x64'). Второй аргумент — это ключ текстуры, загруженной в Phaser ('ground'). Метод возвращает объект Tileset, который нужен для создания слоя.

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

Если слой в Tiled использует тайлы из нескольких наборов, при создании слоя в Phaser нужно передать массив этих наборов. Есть два эквивалентных способа.

// Способ 1: Передать массив объектов Tileset
map.createLayer('Tile Layer 1', [ groundTiles, itemTiles, platformTiles ]);

// Способ 2: Передать массив строк с именами тайлсетов из Tiled
// map.createLayer('Tile Layer 1', [ 'kenny_ground_64x64', 'kenny_items_64x64', 'kenny_platformer_64x64' ]);

Первый способ надежнее, так как вы явно используете уже связанные объекты. Второй способ короче, но Phaser сам будет искать связанные текстуры по имени тайлсета. После вызова createLayer карта отобразится на экране со всеми тайлами из указанных наборов.

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

Поскольку карта может быть большой, настраиваем камеру и добавляем скроллинг. Сначала устанавливаем границы камеры по размерам карты в пикселях.

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

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

Обновление состояния в `update()`

Чтобы элементы управления камерой работали, необходимо обновлять состояние созданного контроллера каждый кадр.

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

Метод update объекта FixedKeyControl считывает состояние назначенных клавиш и плавно перемещает камеру в заданном направлении. Параметр delta (разница во времени между кадрами) обеспечивает плавное движение, не зависящее от частоты кадров.

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

Использование нескольких tilesets в одном слое — мощный и стандартный прием при создании сложных уровней. Основная задача разработчика — корректно связать имена из Tiled JSON с загруженными в Phaser текстурами. Для экспериментов попробуйте

  1. Создать свою карту в Tiled, смешав 2-3 небольших набора тайлов
  2. Добавить отдельные слои для фона и переднего плана, управляя их глубиной
  3. Настроить столкновения (setCollision) для тайлов из определенного набора, чтобы создать физическую геометрию уровня