О чем этот пример
Создание реалистичного освещения и объёма в 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() для отладки и создания собственных динамических текстур на основе загруженных данных.
