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

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

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

Живой запуск

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

Исходный код


class Example extends Phaser.Scene
{
    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');
        const layer = map.createLayer(0, tiles, 0, 0);
    }
}

const config = {
    type: Phaser.AUTO,
    width: 11 * 16, // Number of tiles * size of the tile
    height: 10 * 16,
    zoom: 4,
    parent: 'phaser-example',
    pixelArt: true,
    scene: Example
};

const game = new Phaser.Game(config);

Подготовка: загрузка тайлсета

Перед созданием карты нам нужен набор изображений-тайлов — тайлсет. В методе preload() мы загружаем одно спрайт-изображение, которое содержит все необходимые тайлы, расположенные в виде сетки.

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

Метод this.load.setBaseURL() задает базовый URL для последующих загрузок, что удобно, если все ресурсы хранятся в одной директории. Затем this.load.image() загружает изображение тайлсета и назначает ему ключ 'mario-tiles'. Этот ключ мы позже используем для создания тайлсета в памяти игры.

Данные уровня: карта как массив чисел

Сердце метода — представление уровня в виде двумерного массива (массива массивов). Каждое число в этом массиве соответствует индексу тайла в тайлсете. Ноль (`0`) обычно обозначает пустую клетку (отсутствие тайла).

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

Каждый вложенный массив — это строка (ось Y) на игровой карте. Каждый элемент внутри этого массива — столбец (ось X). Таким образом, level[1][0] (вторая строка, первый столбец) содержит значение `0`. Этот формат интуитивно понятен для чтения и редактирования прямо в коде, что отлично подходит для небольших уровней.

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

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

const map = this.make.tilemap({ data: level, tileWidth: 16, tileHeight: 16 });
const tiles = map.addTilesetImage('mario-tiles');
const layer = map.createLayer(0, tiles, 0, 0);

1. this.make.tilemap() создает объект тайловой карты. В его конфигурацию мы передаем наш массив level и критически важные параметры размера одного тайла: tileWidth и tileHeight. Без них Phaser не сможет корректно разрезать тайлсет и отобразить карту. 2. map.addTilesetImage() связывает загруженное изображение по ключу 'mario-tiles' с нашей картой, создавая тайлсет. Phaser автоматически разрежет изображение на тайлы, используя указанные ранее tileWidth и tileHeight. 3. map.createLayer() создает визуальный слой. Первый аргумент (`0) — это индекс слоя в карте (в нашем случае он один). Второй аргумент (tiles) — тайлсет для отрисовки. Третий и четвертый аргументы (0, 0`) — это координаты X и Y для отрисовки слоя на сцене.

Настройка конфигурации игры

Конфигурация игры (config) специально подогнана под наш пример, чтобы отобразить карту целиком и в увеличенном масштабе.

const config = {
    type: Phaser.AUTO,
    width: 11 * 16, // Number of tiles * size of the tile
    height: 10 * 16,
    zoom: 4,
    parent: 'phaser-example',
    pixelArt: true,
    scene: Example
};

- width и height рассчитаны как количество тайлов в ширину и высоту карты, умноженное на размер тайла. Наш массив level имеет размер 11x11, но отображаемая область задана 11x10 (последняя строка с полом будет обрезана). - zoom: 4 увеличивает весь холст игры в 4 раза, делая маленькие 16-пиксельные тайлы хорошо различимыми. - pixelArt: true включает специальный режим рендеринга для пиксельной графики, который отключает сглаживание при масштабировании, сохраняя четкие границы пикселей. - scene: Example указывает, что начальной сценой будет наш созданный класс.

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

Создание тайл-карт из массива — это мощный и быстрый способ наполнить ваш прототип игровым пространством. Вы получили полный контроль над структурой уровня прямо в JavaScript. Для экспериментов попробуйте

  1. изменить числа в массиве level, чтобы спроектировать новую площадку
  2. сгенерировать массив level процедурно, используя циклы и условия
  3. добавить второй слой карты (например, для декораций поверх земли), создав еще один массив и вызвав map.createLayer(1, tiles, 0, 0). Этот метод станет отличной основой для создания прототипов платформеров, головоломок или топ-даун уровней