О чем этот пример
Создание атмосферных 2D-игр с динамическим освещением — мощный способ погрузить игрока в ваш мир. Phaser позволяет реализовать это, используя карты нормалей (normal maps) для спрайтов. В этой статье мы разберем, как загрузить текстуру и её карту нормалей из атласа, а затем расставить объекты со включенной реакцией на свет. Этот подход добавляет глубину и реализм сцене без необходимости перехода на 3D.
Версия 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({
key: 'megaset',
textureURL: 'texture-packer-atlas-with-normals-0.png',
normalMap: 'texture-packer-atlas-with-normals-0_n.png',
atlasURL: '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() мы используем метод this.load.atlas(), передавая ему объект конфигурации. Это позволяет загрузить не только основной атлас спрайтов, но и связанную с ним карту нормалей в одном вызове.
this.load.atlas({
key: 'megaset',
textureURL: 'texture-packer-atlas-with-normals-0.png',
normalMap: 'texture-packer-atlas-with-normals-0_n.png',
atlasURL: 'texture-packer-atlas-with-normals.json'
});
- key: Уникальный идентификатор для обращения к загруженному атласу.
- textureURL: Путь к основному изображению атласа (цветная текстура).
- normalMap: Путь к изображению карты нормалей. Именно эта текстура будет использоваться для расчетов освещения, определяя, как свет падает на «поверхность» спрайта.
- atlasURL: Путь к JSON-файлу, который описывает расположение отдельных кадров (спрайтов) внутри основного атласа.
Важно использовать WEBGL-рендерер в конфигурации игры, так как освещение не поддерживается в CANVAS.
Включение системы освещения и создание источника света
Прежде чем добавлять спрайты, необходимо активировать систему освещения в сцене. Это делается в методе create().
const light = this.lights.addLight(0, 0, 200);
this.lights.enable().setAmbientColor(0x555555);
- `this.lights.enable()`: Включает систему динамического освещения для текущей сцены.
- `this.lights.addLight(x, y, radius)`: Создает точечный источник света с заданными координатами и радиусом свечения. Возвращает объект света, который мы можем перемещать.
- `setAmbientColor(color)`: Устанавливает цвет окружающего (фонового) освещения. Темный цвет (например, `0x555555`) необходим, чтобы эффект от динамических источников света был хорошо заметен.
Для интерактивности мы привязываем положение источника света к курсору мыши:
this.input.on('pointermove', pointer => {
light.x = pointer.x;
light.y = pointer.y;
});
Получение кадров атласа и создание освещаемых спрайтов
После загрузки атласа мы можем получить из него список всех доступных кадров (отдельных спрайтов).
const atlasTexture = this.textures.get('megaset');
const frames = atlasTexture.getFrameNames();
- this.textures.get('megaset'): Получаем ссылку на загруженную текстуру атласа по её ключу.
- atlasTexture.getFrameNames(): Возвращает массив строк с именами всех кадров, описанных в JSON-атласе.
Чтобы спрайты появлялись в случайном порядке, перемешиваем массив frames с помощью встроенной утилиты Phaser.Utils.Array.Shuffle().
Затем в цикле мы создаем несколько изображений в случайных позициях.
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);
}
- Phaser.Math.Between(min, max): Генерирует случайное целое число в заданном диапазоне для координат X и Y.
- this.add.image(x, y, 'megaset', frames[i]): Создает объект Image (спрайт) в позиции (x, y), используя атлас 'megaset' и конкретный кадр frames[i].
- .setLighting(true): Это самый важный вызов. Он указывает движку, что данный спрайт должен реагировать на динамическое освещение сцены, используя загруженную ранее карту нормалей. Без этого метода спрайт будет отображаться плоским, независимо от источников света.
Что попробовать дальше
Теперь у вас есть рабочая сцена, где множество спрайтов из атласа реалистично освещаются движущимся источником света. Для экспериментов попробуйте: добавить несколько источников света разного цвета и радиуса, изменить параметры окружающего освещения setAmbientColor(), анимировать источники света независимо от курсора или применить фильтры к самой сцене для создания эффекта тумана или цветовой коррекции.
