О чем этот пример
Normal Map (карта нормалей) — это текстура, которая симулирует рельеф поверхности, не изменяя её геометрию. В Phaser 3 вы можете загрузить её вместе с основной диффузной текстурой всего одной строкой кода. Это открывает простой путь к визуальному обогащению 2D-игр: ваши спрайты будут выглядеть объёмными и реагировать на источники света, оставаясь при этом плоскими изображениями. В этой статье мы разберём, как правильно загружать связку текстур и убедиться, что всё загрузилось корректно.
Версия 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('robot', [ 'assets/pics/equality-by-ragnarok.png', '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);
Загрузка связки текстур: диффузная + Normal Map
Ключевой метод для загрузки — this.load.image(). Обычно ему передают ключ и путь к одному изображению. Однако, чтобы загрузить Normal Map вместе с основной текстурой, вторым аргументом нужно передать массив из двух строк.
Первым элементом массива должен быть путь к основному (диффузному) изображению, вторым — путь к карте нормалей. Phaser автоматически свяжет эти две текстуры под одним ключом.
this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.image('robot', [
'assets/pics/equality-by-ragnarok.png',
'assets/normal-maps/equality-by-ragnarok_n.png'
]);
После такой загрузки ключ 'robot' будет ссылаться на текстуру, у которой есть как основное изображение, так и Normal Map. Это необходимо для последующего использования со светом (Lights2D).
Создание спрайта и проверка загрузки
После загрузки в методе create() мы можем создать изображение обычным способом, используя тот же ключ.
const robot = this.add.image(-300, 0, 'robot').setOrigin(0);
В данном примере спрайт размещён за левой границей экрана (x = -300), потому что основная цель кода — не показать его в игре, а продемонстрировать техническую сторону загрузки. Следующий блок кода служит исключительно для визуальной проверки: он рисует загруженную Normal Map на отдельном холсте (CanvasTexture) и отображает этот холст на сцене.
Это полезный приём для отладки, чтобы убедиться, что Normal Map загрузилась корректно и соответствует ожиданиям по размерам и содержимому.
Создание CanvasTexture для отладки Normal Map
Phaser позволяет создавать текстуры на лету. Класс TextureManager (доступный через this.textures) предоставляет метод createCanvas().
Сначала мы создаём пустую текстуру на основе холста с заданным ключом и размерами.
const canvasTexture = this.textures.createCanvas('normalMap', 400, 600);
Затем, используя контекст рисования этого холста (canvasTexture.context), мы вручную отрисовываем на нём изображение из источника данных текстуры нашего спрайта. Важно: метод getDataSourceImage() возвращает HTMLImageElement, связанный с текстурой.
canvasTexture.context.drawImage(robot.texture.getDataSourceImage(), -300, 0);
Обратите внимание на координаты (-300, 0). Мы сдвигаем изображение, потому что исходный спрайт robot был создан с координатой x = -300. Чтобы нарисовать его в начале холста, мы компенсируем этот сдвиг отрицательной координатой.
После внесения изменений в холст текстуру необходимо обновить, чтобы Phaser пересчитал её внутренний WebGL-текстурный объект.
canvasTexture.refresh();
И наконец, создаём изображение на сцене, используя эту новую, только что созданную текстуру.
const robotMap = this.add.image(400, 0, 'normalMap').setOrigin(0);
В результате рядом мы увидим саму Normal Map — обычно это синеватое изображение, где цвет кодирует направление нормалей поверхности.
Конфигурация игры и запуск
Конфигурация игры стандартна. Важно, чтобы размеры игрового холста (width и height) были достаточными для отображения всех элементов, которые мы создаём в примере (основное изображение и его Normal Map).
const config = {
type: Phaser.AUTO,
width: 800,
height: 600,
parent: 'phaser-example',
scene: Example
};
const game = new Phaser.Game(config);
При запуске этой конфигурации вы увидите два изображения: слева — обычная диффузная текстура, справа — её Normal Map, извлечённая и отрисованная на холсте. Это подтверждает успешную загрузку.
Что попробовать дальше
Загрузка Normal Map в Phaser 3 — это простой процесс, сводящийся к передаче массива путей в this.load.image(). После загрузки текстура готова к использованию с системой освещения Lights2D. Для экспериментов попробуйте
- Добавить источник света (
this.lights.addLight()) и включить рендеринг света на сцене, чтобы увидеть, как Normal Map создаёт иллюзию объёма - Использовать другие типы карт (например, Specular Map), загружая их третьим элементом массива
- Автоматизировать процесс отладки, написав функцию, которая для любого ключа текстуры создаёт и отображает её Normal Map в углу экрана
