О чем этот пример

Визуальная атмосфера — ключевой элемент игрового погружения. Phaser 3 предоставляет мощную, но простую в использовании систему освещения на основе WebGL. В этой статье мы разберём, как добавить в сцену один динамический источник света, который будет автоматически взаимодействовать с текстурой объекта, создавая эффект объёма и глубины. Это отличная отправная точка для создания мрачных подземелий, таинственных локаций или просто добавления визуального шарма вашим 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('brick', [ 'assets/normal-maps/brick.jpg', 'assets/normal-maps/brick_n.png' ]);
        this.load.image('logo', 'assets/sprites/atari130xe.png');
    }

    create ()
    {
        this.add.image(400, 300, 'brick').setLighting(true);

        this.add.image(400, 300, 'logo');

        this.lights.enable().setAmbientColor(0x555555);

        const hsv = Phaser.Display.Color.HSVColorWheel();

        const radius = 80;
        const intensity = 6;
        const x = radius;
        const y = 0;

        const color = hsv[10].color;

        const light = this.lights.addLight(400, y, radius, color, intensity);

        this.tweens.add({
            targets: light,
            y: 600,
            yoyo: true,
            repeat: -1,
            ease: 'Sine.easeInOut',
            duration: 2000
        });
    }
}

const config = {
    type: Phaser.WEBGL,
    width: 800,
    height: 600,
    parent: 'phaser-example',
    backgroundColor: '#000000',
    scene: Example
};

const game = new Phaser.Game(config);

Подготовка сцены и загрузка ассетов

Прежде чем работать со светом, необходимо подготовить сцену и загрузить специальные ассеты. Ключевой момент — использование текстуры с нормальной картой (normal map). Нормальная карта — это дополнительное изображение, которое кодирует информацию о направлении поверхности (её «неровностях»). Именно эта информация позволяет плоскому 2D-спрайту корректно взаимодействовать с источником света, создавая иллюзию объёма.

В методе preload мы загружаем два изображения: основную текстуру и её нормальную карту.

this.load.image('brick', [ 'assets/normal-maps/brick.jpg', 'assets/normal-maps/brick_n.png' ]);
this.load.image('logo', 'assets/sprites/atari130xe.png');

Обратите внимание на синтаксис загрузки: массив передаётся как второй аргумент. Phaser автоматически связывает основное изображение (brick.jpg) и карту нормалей (brick_n.png) под одним ключом 'brick'. Второй спрайт ('logo') загружается без нормальной карты и не будет реагировать на свет.

Создание объектов и включение света

В методе create мы размещаем объекты в мире и активируем систему освещения.

this.add.image(400, 300, 'brick').setLighting(true);
this.add.image(400, 300, 'logo');

Для кирпичной текстуры мы сразу вызываем метод .setLighting(true). Это указывает рендереру, что данный объект должен учитывать освещение и использовать свою нормальную карту.

Далее мы включаем систему освещения для всей сцены и устанавливаем цвет окружающего (фонового) света.

this.lights.enable().setAmbientColor(0x555555);

Метод this.lights.enable() активирует систему. setAmbientColor(0x555555) задаёт серый цвет фоновой подсветки, которая равномерно освещает все объекты на сцене. Без неё области, не попавшие под прямой свет, были бы абсолютно чёрными.

Настройка и анимация источника света

Теперь создадим сам движущийся источник света. Сначала мы получаем палитру цветов в формате HSV (Hue, Saturation, Value), что удобно для плавных цветовых переходов.

const hsv = Phaser.Display.Color.HSVColorWheel();
const color = hsv[10].color;

В данном примере мы берём цвет под индексом 10 (оттенок оранжевого) из полученного массива.

Создаём свет с помощью метода this.lights.addLight().

const light = this.lights.addLight(400, y, radius, color, intensity);

Аргументы метода: 1. **x, y**: начальные координаты источника света. 2. **radius**: радиус действия света в пикселях. 3. **color**: цвет света в числовом формате (например, 0xff5500). 4. **intensity**: интенсивность (яркость) света. Значение 6 делает его очень ярким.

Чтобы свет ожил, мы добавляем к нему твин (анимацию) с помощью this.tweens.add().

this.tweens.add({
    targets: light,
    y: 600,
    yoyo: true,
    repeat: -1,
    ease: 'Sine.easeInOut',
    duration: 2000
});

Эта анимация бесконечно (repeat: -1) двигает свет вниз до координаты y=600 и обратно (yoyo: true), создавая плавное движение по синусоиде (ease: 'Sine.easeInOut') за 2 секунды.

Конфигурация игры: важность WebGL

Система освещения Phaser работает исключительно в контексте WebGL. Это критически важно указать в конфигурации игры.

const config = {
    type: Phaser.WEBGL, // Обязательно WEBGL, а не CANVAS
    width: 800,
    height: 600,
    parent: 'phaser-example',
    backgroundColor: '#000000',
    scene: Example
};

Если установить type: Phaser.CANVAS, освещение работать не будет. Фоновый цвет сцены установлен в чёрный (#000000), чтобы контраст со светом был максимально заметным.

Что попробовать дальше

Вы только что реализовали динамическое освещение с одним источником света в Phaser 3. Это основа для создания сложных световых сцен. Для экспериментов попробуйте изменить параметры света (цвет, радиус, интенсивность), добавить несколько источников с разными траекториями движения или применить свет к анимированным спрайтам. Чтобы углубиться, изучите методы this.lights.addLight для создания цветовых градиентов и настройки аттенюации (затухания света с расстоянием).