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

При импорте тайлсетов из графических редакторов, таких как Tiled, часто возникает ситуация, когда между тайлами присутствуют отступы или поля. Если их не учесть, тайловая карта будет отображаться некорректно: тайлы сместятся, и вы увидите артефакты. Эта статья на практическом примере покажет, как правильно загружать тайлсеты с отступами (spacing) в Phaser, будь то загрузка из JSON-файла Tiled или ручное создание тайлсета. Вы научитесь контролировать параметры `tileMargin` и `tileSpacing`, что критически важно для корректного отображения игровых уровней.

Версия 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('map', 'assets/tilemaps/maps/desert.json');
        this.load.image('Desert', 'assets/tilemaps/tiles/tmw_desert_spacing.png');
        this.load.image('drawtiles-spaced', 'assets/tilemaps/tiles/drawtiles-spaced.png');
    }

    create ()
    {
        // 1 - Tilesets loaded from Tiled will having the spacing properties already set

        const map = this.make.tilemap({ key: 'map' });
        const tiles = map.addTilesetImage('Desert');
        const layer = map.createLayer(0, tiles, 0, 0);
        layer.setScrollFactor(0.5);
        layer.setAlpha(0.75);


        // 2 - Tilesets loaded outside of a Tiled JSON file will need the spacing properties set (if
        // your tileset has a margin or padding)

        const level = [
            [ 2, 2, 2, 2 ],
            [ 2, 2, 2, 2 ],
            [ 2, 2, 2, 2 ],
            [ 2, 2, 2, 2 ]
        ];
        const map2 = this.make.tilemap({ tileWidth: 32, tileHeight: 32, data: level });

        // addTilesetImage Parameters:
        //  tilesetName, key, tileWidth, tileHeight, tileMargin, tileSpacing
        const tiles2 = map2.addTilesetImage('drawtiles-spaced', null, 32, 32, 1, 2);

        const layer2 = map2.createLayer(0, tiles2, 200, 200);
        layer2.setScale(2);


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

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

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

const game = new Phaser.Game(config);

Разбор примера: два подхода к загрузке тайлсетов

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

Оба подхода используют разные методы this.load.* для загрузки ресурсов и разные параметры в функции addTilesetImage. Ключевой момент — правильно передать информацию об отступах в движок.

Подход 1: Загрузка карты из Tiled JSON

Когда вы экспортируете карту из Tiled в формате JSON, информация о тайлсете, включая отступы между тайлами (spacing) и поля (margin), сохраняется внутри файла. Phaser при загрузке такого файла автоматически читает эти данные.

В методе preload() мы загружаем JSON-карту и изображение тайлсета:

this.load.tilemapTiledJSON('map', 'assets/tilemaps/maps/desert.json');
this.load.image('Desert', 'assets/tilemaps/tiles/tmw_desert_spacing.png');

В методе create() создается тайловая карта и слой. Обратите внимание, что при вызове map.addTilesetImage('Desert') не передаются параметры ширины, высоты или отступов. Phaser берет эту информацию из загруженного JSON-файла.

const map = this.make.tilemap({ key: 'map' });
const tiles = map.addTilesetImage('Desert');
const layer = map.createLayer(0, tiles, 0, 0);

Дополнительно для этого слоя задается эффект параллакса (setScrollFactor(0.5)) и полупрозрачность (setAlpha(0.75)). Это сделано для наглядности в примере, чтобы оба слоя не сливались.

Подход 2: Ручное создание карты с кастомным тайлсетом

Если вы загружаете изображение тайлсета отдельно, без метаданных из Tiled, вам необходимо вручную указать все его параметры. В примере тайлсет drawtiles-spaced.png имеет отступы.

Сначала в preload() загружается только изображение:

this.load.image('drawtiles-spaced', 'assets/tilemaps/tiles/drawtiles-spaced.png');

Затем в create() создается простая карта из двумерного массива и объекта конфигурации. Ключевой шаг — вызов addTilesetImage с полным набором параметров:

const map2 = this.make.tilemap({ tileWidth: 32, tileHeight: 32, data: level });
const tiles2 = map2.addTilesetImage('drawtiles-spaced', null, 32, 32, 1, 2);

Параметры функции addTilesetImage в данном случае: 1. tilesetName — имя тайлсета, используемое внутри карты. Здесь передается ключ загруженного изображения. 2. key — ключ загруженного изображения в кэше. Передача null означает, что будет использовано имя из первого параметра. 3. tileWidth — ширина одного тайла в пикселях (32). 4. tileHeight — высота одного тайла в пикселях (32). 5. tileMargin — отступ (margin) от края изображения до первого тайла (1). 6. tileSpacing — расстояние (spacing) между тайлами внутри изображения (2).

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

Управление камерой для навигации

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

Создается объект конфигурации управления controlConfig, который связывает клавиши-стрелки с перемещением камеры. Затем инстанс Phaser.Cameras.Controls.FixedKeyControl обновляется в каждом кадре в методе update().

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);
update (time, delta)
{
    this.controls.update(delta);
}

Это позволяет "пролететь" камерой по сцене и убедиться, что оба слоя отображаются корректно, без смещения тайлов.

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

Корректная работа с отступами в тайлсетах — обязательный навык для создания сложных тайловых уровней. Главный вывод: при загрузке из Tiled JSON Phaser делает всё сам, а при ручной работе с изображением необходимо явно указывать tileMargin и tileSpacing в addTilesetImage. Для экспериментов попробуйте изменить значения отступов в коде второго подхода и посмотрите, как "поедет" отображение тайлов. Также можно создать свой тайлсет в графическом редакторе с нестандартными отступами и загрузить его в игру, используя изученный метод.