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

Ранние платформеры, такие как Super Mario Bros, определили стандарт скроллинговых уровней. В современных играх на Phaser этот принцип оживает благодаря тайловым картам и умной камере. Эта статья покажет, как загрузить карту из Tiled, масштабировать её и реализовать плавное управление камерой с клавиатуры — основа для многих 2D-игр. Вы научитесь связывать JSON-карту из редактора Tiled со спрайтшитом, создавать игровой слой и настраивать камеру, которая следует за действиями игрока или, как в нашем примере, управляется вручную. Это фундамент для построения больших миров, выходящих за пределы одного экрана.

Версия 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('map1', 'assets/tilemaps/maps/super-mario.json');
        this.load.image('tiles1', 'assets/tilemaps/tiles/super-mario.png');
    }

    create ()
    {
        const map1 = this.make.tilemap({ key: 'map1' });
        const tileset1 = map1.addTilesetImage('SuperMarioBros-World1-1', 'tiles1');
        const layer1 = map1.createLayer('World1', tileset1, 0, 0).setScale(2.5);

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

        this.cameras.main.setBounds(0, 0, layer1.x + layer1.width, layer1.height * 3);
    }

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

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

const game = new Phaser.Game(config);

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

Работа начинается с загрузки двух ключевых ресурсов: файла карты в формате JSON, экспортированного из Tiled, и изображения тайлсета (спрайтшита). Phaser позволяет легко разделить данные уровня (расположение тайлов, слои, свойства) и их визуальное представление.

В методе preload мы используем this.load.setBaseURL для указания базового URL, что удобно для загрузки удалённых ресурсов из репозитория с примерами. Далее загружаем саму карту и изображение тайлов.

this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.tilemapTiledJSON('map1', 'assets/tilemaps/maps/super-mario.json');
this.load.image('tiles1', 'assets/tilemaps/tiles/super-mario.png');

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

В методе create мы создаём объект тайловой карты, связываем его с загруженным изображением тайлов и, наконец, создаём видимый слой. Ключевой момент — использование имени тайлсета, заданного в Tiled ('SuperMarioBros-World1-1'), для правильного сопоставления.

Созданный слой сразу масштабируется методом .setScale(2.5), увеличивая пиксель-арт графику для более ретро-вида. Это демонстрирует гибкость Phaser: вы можете менять масштаб уже после создания уровня.

const map1 = this.make.tilemap({ key: 'map1' });
const tileset1 = map1.addTilesetImage('SuperMarioBros-World1-1', 'tiles1');
const layer1 = map1.createLayer('World1', tileset1, 0, 0).setScale(2.5);

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

Чтобы исследовать большую карту, нам нужно управлять камерой. Phaser предоставляет готовый класс Phaser.Cameras.Controls.FixedKeyControl. Сначала создаём объект с конфигурацией, где привязываем стрелки клавиатуры к направлениям движения камеры и задаём скорость её перемещения.

Важно установить границы (bounds) для камеры, чтобы она не уходила за пределы уровня. Границы рассчитываются на основе размеров масштабированного слоя.

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);
this.cameras.main.setBounds(0, 0, layer1.x + layer1.width, layer1.height * 3);

Обновление состояния камеры в игровом цикле

Логика управления камерой должна обновляться каждый кадр. Для этого в методе update мы вызываем метод update объекта this.controls, передавая ему дельту времени (delta). Это обеспечивает плавное и независимое от частоты кадров движение.

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

Конфигурация игры: важность флага pixelArt

Корневая конфигурация игры (config) определяет основные параметры. Обратите внимание на свойство pixelArt. В нашем примере оно установлено в false, что означает стандартную линейную фильтрацию текстур при масштабировании. Для сохранения чёткости пиксель-арт графики (как в оригинальном спрайтшите Mario) это значение нужно установить в true. Это предотвратит размытие тайлов при масштабировании.

const config = {
    type: Phaser.AUTO,
    width: 800,
    height: 600,
    backgroundColor: '#2d2d2d',
    parent: 'phaser-example',
    pixelArt: false, // Измените на true для чёткого пиксель-арта
    scene: Example
};

const game = new Phaser.Game(config);

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

Вы создали базовый движущийся мир: карта загружается, масштабируется и позволяет камере путешествовать по ней с клавиатуры. Это основа для платформера, RPG или исследовательской игры. Для экспериментов попробуйте

  1. Включить режим pixelArt: true в конфиге, чтобы увидеть разницу
  2. Привязать камеру к спрайту игрока вместо ручного управления, используя this.cameras.main.startFollow(sprite)
  3. Добавить несколько слоёв карты (например, для фона и переднего плана) и настроить их глубину