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

Мини-карты — неотъемлемый элемент многих игр, от RPG до стратегий. Они помогают игрокам ориентироваться на большой локации, не теряя общий контекст. В Phaser 3 вы можете легко добавить вторую камеру, которая будет служить такой мини-картой, и анимировать её для создания динамического эффекта обзора или слежения. Эта статья покажет, как создать отдельную камеру-миниатюру, настроить её вид и заставить плавно перемещаться по сгенерированной карте. Этот приём полезен не только для статических карт, но и для отображения положения других игроков, важных объектов или анимации кат-сцен.

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

Живой запуск

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

Исходный код


class Example extends Phaser.Scene
{
    height = 38;
    width = 40;
    t = 0;

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

    create ()
    {
        // Build a random level as a 2D array
        const level = [];
        for (let y = 0; y < this.height; y++)
        {
            const row = [];
            for (let x = 0; x < this.width; x++)
            {
                const tileIndex = Phaser.Math.RND.integerInRange(0, 6);
                row.push(tileIndex);
            }
            level.push(row);
        }

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

        this.cameras.main.setBounds(0, 0, layer.width, layer.height);
        this.minimap = this.cameras.add(200, 10, 100, 100).setZoom(0.2);
        this.minimap.setBackgroundColor('#ffff00');
    }

    update ()
    {
        this.minimap.scrollX = this.width * 32 / 2 + Math.cos(this.t) * 300;
        this.minimap.scrollY = this.height * 32 / 2 + Math.sin(this.t) * 300;
        this.t += 0.025;
    }
}

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

const game = new Phaser.Game(config);

Подготовка сцены и загрузка ассетов

В классе сцены определяются базовые параметры: высота и ширина будущей карты в тайлах, а также переменная `t` для анимации.

В методе preload() загружается единственный tileset — изображение, содержащее все виды тайлов. Обратите внимание на использование setBaseURL() — это удобный способ указать базовый путь для загрузки ресурсов, если они хранятся удалённо.

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

Генерация случайной карты и создание слоя

Вся логика создания игрового мира сосредоточена в create(). Сначала вручную генерируется двумерный массив level, представляющий карту. Каждый элемент массива — случайное число от 0 до 6, что соответствует индексу тайла в tileset.

const level = [];
for (let y = 0; y < this.height; y++)
{
    const row = [];
    for (let x = 0; x < this.width; x++)
    {
        const tileIndex = Phaser.Math.RND.integerInRange(0, 6);
        row.push(tileIndex);
    }
    level.push(row);
}

Затем этот массив преобразуется в объект Tilemap с помощью this.make.tilemap(). Важно указать размер тайла. После создания tileset на основе загруженного изображения, формируется визуальный слой layer.

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

Настройка основной камеры и создание мини-карты

Основной камере задаются границы (setBounds), равные размерам созданного слоя. Это предотвращает её выход за пределы игрового мира.

Самое интересное — создание второй камеры, которая будет мини-картой. Это делается методом this.cameras.add(), куда передаются координаты, ширина и высота новой камеры на экране.

Ключевой параметр — setZoom(0.2). Он уменьшает масштаб отображения, позволяя уместить большую карту в маленький прямоугольник. Фон мини-карты подсвечивается жёлтым цветом для наглядности.

this.cameras.main.setBounds(0, 0, layer.width, layer.height);
this.minimap = this.cameras.add(200, 10, 100, 100).setZoom(0.2);
this.minimap.setBackgroundColor('#ffff00');

Анимация движения мини-карты

Чтобы мини-карта не была статичной, в методе update() реализовано её плавное движение по кругу. Это имитирует, например, автоматический обзор локации.

Переменная this.t плавно увеличивается, выступая в роли угла. Синус и косинус от этого угла, умноженные на радиус (300), задают смещение относительно центра карты. Центр рассчитывается как половина от общего размера мира в пикселях (this.width * 32 / 2).

update ()
{
    this.minimap.scrollX = this.width * 32 / 2 + Math.cos(this.t) * 300;
    this.minimap.scrollY = this.height * 32 / 2 + Math.sin(this.t) * 300;
    this.t += 0.025;
}

Изменяя свойства scrollX и scrollY мини-камеры, мы управляем той областью игрового мира, которая в неё попадает.

Конфигурация игры

Код завершается стандартной конфигурацией игры. Обратите внимание на два важных параметра: - pixelArt: true — включает сглаживание текстур для пиксельной графики. - scene: Example — указывает класс сцены, который будет запущен.

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

const game = new Phaser.Game(config);

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

Вы создали динамическую мини-карту, которая является отдельной камерой со своим zoom и положением. Это мощный паттерн. Для экспериментов попробуйте

  1. Привязать движение мини-камеры не к синусу, а к координатам спрайта игрока для классического слежения
  2. Добавить на мини-карту маркеры (Graphics) для обозначения ключевых точек
  3. Сделать мини-карту интерактивной, обрабатывая клики по ней для перемещения основной камеры