О чем этот пример
Освещение — мощный инструмент для создания атмосферы и глубины в 2D-играх. В Phaser можно легко добавить динамичные источники света, которые реагируют на взаимодействие игрока. Эта статья покажет, как совместить систему освещения с анимацией и физикой Arcade, чтобы создать живую сцену, где анимированные спрайты, отражаясь от границ, освещаются перемещаемым источником света.
Версия 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('bg', [ 'assets/textures/gold.png', 'assets/textures/gold-n.png' ]);
this.load.spritesheet('face', [ 'assets/sprites/metalface78x92.png', 'assets/sprites/metalface78x92-n.png' ], { frameWidth: 78, frameHeight: 92 });
}
create ()
{
this.lights.enable();
this.lights.setAmbientColor(0x555555);
this.add.sprite(400, 300, 'bg').setLighting(true).setAlpha(0.5);
this.anims.create({
key: 'eyes',
frames: this.anims.generateFrameNumbers('face', { start: 0, end: 3 }),
frameRate: 14,
repeat: -1
});
for (let i = 0; i < 6; i++)
{
const x = Phaser.Math.Between(100, 700);
const y = Phaser.Math.Between(100, 500);
const face = this.physics.add.sprite(x, y, 'face');
face.setLighting(true);
face.play('eyes');
face.setVelocity(Phaser.Math.Between(-100, 100), Phaser.Math.Between(-100, 100));
face.setBounce(1, 1);
face.setCollideWorldBounds(true);
}
const light = this.lights.addLight(400, 300, 200).setIntensity(2);
this.input.on('pointermove', pointer =>
{
light.x = pointer.x;
light.y = pointer.y;
});
}
}
const config = {
type: Phaser.WEBGL,
parent: 'phaser-example',
width: 800,
height: 600,
physics: {
default: 'arcade'
},
scene: Example
};
const game = new Phaser.Game(config);
Подготовка ресурсов: нормальные карты и спрайтшит
Для работы с динамическим освещением в WebGL-режиме Phaser требуются не только обычные текстуры, но и нормальные карты (normal maps). Нормальная карта кодирует информацию о поверхности объекта (неровности, углы), что позволяет свету реалистично "обтекать" его, создавая иллюзию объема.
В методе preload мы загружаем два набора ресурсов. Обратите внимание на синтаксис загрузки с массивами: первый элемент — цветная текстура, второй — соответствующая нормальная карта.
this.load.image('bg', [ 'assets/textures/gold.png', 'assets/textures/gold-n.png' ]);
this.load.spritesheet('face', [ 'assets/sprites/metalface78x92.png', 'assets/sprites/metalface78x92-n.png' ], { frameWidth: 78, frameHeight: 92 });
Спрайтшит face загружается с указанием размеров кадра. Нормальная карта для него (-n.png) загружается автоматически тем же вызовом.
Включение освещения и настройка сцены
В начале метода create необходимо активировать систему освещения для сцены. Без этого все последующие манипуляции со светом не будут иметь эффекта.
this.lights.enable();
this.lights.setAmbientColor(0x555555);
this.lights.enable() включает менеджер освещения. this.lights.setAmbientColor(0x555555) устанавливает цвет окружающего (фонового) света. Это базовый уровень освещенности, который виден даже в тени. Темно-серый цвет (0x555555) создает контрастную основу для динамического света.
Затем мы добавляем фоновый спрайт и настраиваем его взаимодействие со светом.
this.add.sprite(400, 300, 'bg').setLighting(true).setAlpha(0.5);
Метод setLighting(true) сообщает движку, что этот объект должен реагировать на источники света. setAlpha(0.5) уменьшает непрозрачность, чтобы фон не перетягивал на себя внимание.
Создание анимации и физических объектов
Перед созданием спрайтов определяем анимацию. Phaser использует систему анимаций на основе ключей.
this.anims.create({
key: 'eyes',
frames: this.anims.generateFrameNumbers('face', { start: 0, end: 3 }),
frameRate: 14,
repeat: -1
});
generateFrameNumbers генерирует массив кадров из спрайтшита face с индексами от 0 до 3. repeat: -1 задает бесконечное повторение анимации.
Далее в цикле создаются шесть физических спрайтов со случайными параметрами.
for (let i = 0; i < 6; i++)
{
const x = Phaser.Math.Between(100, 700);
const y = Phaser.Math.Between(100, 500);
const face = this.physics.add.sprite(x, y, 'face');
face.setLighting(true);
face.play('eyes');
face.setVelocity(Phaser.Math.Between(-100, 100), Phaser.Math.Between(-100, 100));
face.setBounce(1, 1);
face.setCollideWorldBounds(true);
}
this.physics.add.sprite создает спрайт с телом Arcade Physics. setLighting(true) включает для него реакцию на свет. setVelocity задает случайную начальную скорость по осям X и Y. setBounce(1, 1) определяет идеальный отскок (коэффициент 1) от любых поверхностей. setCollideWorldBounds(true) включает столкновение с границами мира, определенными в конфигурации игры.
Динамический источник света и привязка к курсору
Сердцевина интерактивности — создание источника света и его привязка к движению указателя мыши.
const light = this.lights.addLight(400, 300, 200).setIntensity(2);
this.lights.addLight(x, y, radius) создает точечный источник света с начальными координатами и радиусом воздействия в пикселях. Метод setIntensity(2) удваивает интенсивность свечения по сравнению со значением по умолчанию.
Чтобы свет следовал за курсором, мы подписываемся на событие pointermove.
this.input.on('pointermove', pointer =>
{
light.x = pointer.x;
light.y = pointer.y;
});
В обработчике события мы просто обновляем координаты `xиyсозданного объекта света, используя координаты указателя из объектаpointer`.
Конфигурация игры: WebGL и физика
Для работы освещения и нормальных карт критически важен рендерер. Необходимо использовать WebGL.
const config = {
type: Phaser.WEBGL,
parent: 'phaser-example',
width: 800,
height: 600,
physics: {
default: 'arcade'
},
scene: Example
};
Ключевой параметр — type: Phaser.WEBGL. Освещение не работает в режиме Phaser.CANVAS. В блоке physics активируется система Arcade Physics, которую мы использовали для спрайтов с методами setVelocity и setBounce.
Что попробовать дальше
Вы создали интерактивную сцену, где анимированные физические объекты оживают под динамическим светом. Для экспериментов попробуйте: добавить несколько источников света разного цвета с помощью setColor, изменить setAmbientColor на более темный или цветной оттенок, управлять светом не курсором, а через this.input.keyboard, создавая световые вспышки, или привязать источник света к одному из движущихся спрайтов, сделав его своего рода фонариком.
