О чем этот пример
Хотите, чтобы ваша 2D-игра на Phaser выглядела объёмнее и атмосфернее? Ключ к этому — динамическое освещение и карты нормалей (normal maps). Вместо статичных плоских спрайтов вы получите объекты, которые реагируют на свет и тень, создавая иллюзию глубины. В этой статье мы на практике разберём пример, который покажет, как загрузить текстурный атлас с нормал-мапами и интегрировать его в систему освещения движка.
Версия 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.setPath('assets/loader-tests/');
this.load.atlas('megaset', [ 'texture-packer-atlas-with-normals-0.png', 'texture-packer-atlas-with-normals-0_n.png' ], 'texture-packer-atlas-with-normals.json');
}
create ()
{
const light = this.lights.addLight(0, 0, 200);
this.lights.enable().setAmbientColor(0x555555);
this.input.on('pointermove', pointer =>
{
light.x = pointer.x;
light.y = pointer.y;
});
const atlasTexture = this.textures.get('megaset');
const frames = atlasTexture.getFrameNames();
Phaser.Utils.Array.Shuffle(frames);
for (let i = 0; i < frames.length; i++)
{
const x = Phaser.Math.Between(100, 700);
const y = Phaser.Math.Between(100, 500);
this.add.image(x, y, 'megaset', frames[i]).setLighting(true);
}
}
}
const config = {
type: Phaser.WEBGL,
width: 800,
height: 600,
parent: 'phaser-example',
scene: Example
};
const game = new Phaser.Game(config);
Загрузка текстурного атласа с картами нормалей
Всё начинается с этапа предзагрузки (preload). В Phaser для загрузки атласов, созданных, например, в Texture Packer, используется метод this.load.atlas(). Особенность нашего примера в том, что мы загружаем не только цветовую текстуру, но и соответствующую ей карту нормалей.
Карта нормалей — это специальное изображение, где цвет каждого пикселя кодирует информацию о направлении поверхности (нормали). Это позволяет рассчитать, как свет должен падать на плоский 2D-спрайт, создавая эффект объёма и рельефа.
В примере мы видим, что методу передаётся массив из двух путей к изображениям.
this.load.atlas('megaset', [ 'texture-packer-atlas-with-normals-0.png', 'texture-packer-atlas-with-normals-0_n.png' ], 'texture-packer-atlas-with-normals.json');
Первый файл (...-0.png) — это обычный цветовой атлас. Второй файл с суффиксом _n (...-0_n.png) — это карта нормалей. Третий аргумент — JSON-файл, описывающий структуру атласа (рамки и их координаты). Phaser автоматически связывает основной атлас и карту нормалей по их ключу 'megaset'.
Настройка системы освещения на сцене
Чтобы использовать освещение в Phaser, необходимо активировать систему Lights и настроить её. Это делается в методе create.
Сначала создаётся источник света — точечная лампа. Метод this.lights.addLight() принимает начальные координаты X, Y и радиус освещения.
const light = this.lights.addLight(0, 0, 200);
Далее система освещения явно включается вызовом this.lights.enable(). Без этого шага свет не будет работать. Также устанавливается фоновый (ambient) свет. Он добавляет постоянную, равномерную подсветку ко всем объектам, чтобы области в тени не становились completamente чёрными.
this.lights.enable().setAmbientColor(0x555555);
Затем мы привязываем источник света к движению указателя мыши. При каждом перемещении мыши (pointermove) координаты лампы обновляются.
this.input.on('pointermove', pointer => {
light.x = pointer.x;
light.y = pointer.y;
});
Создание спрайтов с поддержкой освещения
Теперь нужно создать объекты, которые будут реагировать на наш источник света. Для этого мы получаем доступ к загруженной текстуре по её ключу.
const atlasTexture = this.textures.get('megaset');
Из текстуры можно получить массив имён всех отдельных кадров (frames), которые в неё входят.
const frames = atlasTexture.getFrameNames();
Чтобы спрайты на сцене располагались в случайном порядке, имена кадров перемешиваются с помощью утилиты Phaser.Utils.Array.Shuffle().
Далее в цикле для каждого имени кадра создаётся изображение (this.add.image). Ключевой момент — вызов метода .setLighting(true) для этого изображения. Именно этот флаг указывает Phaser, что данный спрайт должен взаимодействовать с системой освещения и использовать привязанную к его текстуре карту нормалей.
this.add.image(x, y, 'megaset', frames[i]).setLighting(true);
Координаты X и Y для каждого спрайта генерируются случайным образом в заданных пределах с помощью Phaser.Math.Between().
Что попробовать дальше
Как видите, добавить продвинутое динамическое освещение с нормал-мапами в Phaser довольно просто. Основные шаги: корректно загрузить связанные текстуры, включить систему света и не забыть вызвать .setLighting(true) для нужных спрайтов. Для экспериментов попробуйте изменить цвет и интенсивность окружающего света (setAmbientColor), добавить несколько статических или движущихся источников света разного цвета и радиуса, или применить освещение только к определённым слоям объектов, создавая более глубокую композицию сцены.
