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

Создание реалистичного освещения и объёма в 2D-графике — задача для нормальных карт (Normal Maps). В игровом движке Phaser 3 можно легко загружать их вместе с основным изображением, что сразу же открывает возможности для динамического свечения и теней. В этой статье мы разберём пример, показывающий, как корректно загрузить пару «изображение — нормальная карта» с помощью объекта конфигурации и проверить результат, отрисовав карту на канвасе.

Версия 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({
            key: 'robot',
            url: 'assets/pics/equality-by-ragnarok.png',
            normalMap: 'assets/normal-maps/equality-by-ragnarok_n.png'
        });
    }

    create ()
    {
        const robot = this.add.image(-300, 0, 'robot').setOrigin(0);

        //  The following just displays the normal map on-screen, so you can see that it loaded properly

        const canvasTexture = this.textures.createCanvas('normalMap', 400, 600);

        canvasTexture.context.drawImage(robot.texture.getDataSourceImage(), -300, 0);

        canvasTexture.refresh();

        const robotMap = this.add.image(400, 0, 'normalMap').setOrigin(0);
    }
}

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

const game = new Phaser.Game(config);

Конфигурация загрузки через объект

Традиционный метод this.load.image('key', 'url') позволяет загрузить только основную текстуру. Для загрузки нормальной карты необходимо использовать расширенный синтаксис с объектом конфигурации. Это делает код более структурированным и удобным для добавления дополнительных параметров в будущем.

this.load.image({
    key: 'robot',
    url: 'assets/pics/equality-by-ragnarok.png',
    normalMap: 'assets/normal-maps/equality-by-ragnarok_n.png'
});

В этом объекте: - key — уникальный идентификатор для последующего использования текстуры. - url — путь к основному (диффузному) изображению. - normalMap — путь к файлу нормальной карты. Phaser автоматически свяжет её с основной текстурой по ключу 'robot'.

Создание изображения и доступ к данным

После загрузки в методе create() мы создаём спрайт обычным способом, используя ключ 'robot'. Нормальная карта уже «привязана» к этой текстуре в памяти движка и будет использоваться системами освещения, например, при добавлении источников света с помощью this.lights.

const robot = this.add.image(-300, 0, 'robot').setOrigin(0);

Обратите внимание на setOrigin(0). Это устанавливает точку начала координат спрайта в его левый верхний угол. Мы смещаем изображение за левую границу экрана на 300 пикселей (x: -300), чтобы основное изображение не отображалось в рабочей области сцены, так как цель примера — визуализировать саму нормальную карту.

Визуализация нормальной карты на Canvas

Чтобы наглядно убедиться, что нормальная карта загрузилась корректно, пример создаёт отдельную текстуру на основе канваса и отрисовывает в неё данные.

const canvasTexture = this.textures.createCanvas('normalMap', 400, 600);
canvasTexture.context.drawImage(robot.texture.getDataSourceImage(), -300, 0);
canvasTexture.refresh();

Пояснение к коду: 1. this.textures.createCanvas('normalMap', 400, 600) — создаёт в менеджере текстур Phaser новую пустую текстуру типа Canvas с заданными размерами и ключом 'normalMap'. 2. robot.texture.getDataSourceImage() — это важный вызов. Он возвращает HTMLImageElement, который является источником данных для текстуры 'robot'. Для текстуры, загруженной с нормальной картой, этот метод вернёт изображение нормальной карты, а не основное диффузное. 3. drawImage(..., -300, 0) — метод 2D-контекста канваса отрисовывает полученное изображение. Смещение -300 компенсирует первоначальное положение спрайта robot, чтобы нормальная карта встала в начало координат канваса. 4. refresh() — сообщает текстуре, что её канвас был изменён, и её необходимо обновить в GPU.

Отображение результата

После подготовки текстуры канваса её можно отобразить на сцене как обычное изображение. Это позволяет увидеть, как выглядит загруженная нормальная карта — она обычно имеет синевато-фиолетовые оттенки.

const robotMap = this.add.image(400, 0, 'normalMap').setOrigin(0);

Здесь создаётся второе изображение, которое использует только что созданную текстуру 'normalMap'. Оно позиционируется по координате x: 400, то есть в правой половине экрана, что позволяет сравнить (в других экспериментах) основное изображение и его нормальную карту.

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

Использование объекта конфигурации в this.load.image() — это правильный способ загрузки связанных ресурсов, таких как нормальные карты, в Phaser 3. После загрузки движок автоматически управляет этими данными. Для экспериментов попробуйте: 1. Добавить источник света (this.lights.addLight()) и включить освещение для сцены, чтобы увидеть эффект от нормальной карты на основном спрайте robot. 2. Загрузить другие типы карт, например, specularMap, используя тот же паттерн с объектом конфигурации. 3. Использовать robot.texture.getDataSourceImage() для отладки и создания собственных динамических текстур на основе загруженных данных.