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

Световые эффекты — мощный инструмент для создания атмосферы и глубины в 2D-играх. В Phaser 3 система освещения позволяет оживлять статичные сцены, делая их интерактивными и визуально привлекательными. В этой статье мы разберём практический пример, где динамический источник света взаимодействует с bitmap-текстом и спрайтами, реагируя на движения и клики мыши. Вы научитесь подключать и настраивать освещение для нестандартных объектов, что откроет новые возможности для визуального оформления ваших проектов.

Версия 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('sonic', 'assets/sprites/sonic_havok_sanity.png');
        this.load.image('bg', [ 'assets/textures/gold.png', 'assets/textures/gold-n.png' ]);
        this.load.bitmapFont('ice', [ 'assets/fonts/bitmap/iceicebaby.png', 'assets/fonts/bitmap/iceicebaby_n.png' ], 'assets/fonts/bitmap/iceicebaby.xml');
    }

    create ()
    {
        this.add.sprite(400, 300, 'bg')
        .setLighting(true)
        .setAlpha(0.2);

        this.add.sprite(680, 600, 'sonic').setOrigin(0.5, 1);

        this.add.bitmapText(400, 300, 'ice', 'Bitmap Text\nwith Lights', 110)
        .setCenterAlign()
        .setOrigin(0.5)
        .setLighting(true);

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

        const spotlight = this.lights.addLight(400, 300, 280).setIntensity(3);

        this.input.on('pointermove', pointer =>
        {

            spotlight.x = pointer.x;
            spotlight.y = pointer.y;

        });

        const colors = [
            0xffffff, 0xff0000, 0x00ff00, 0x00ffff, 0xff00ff, 0xffff00
        ];

        let currentColor = 0;

        this.input.on('pointerdown', pointer =>
        {

            currentColor++;

            if (currentColor === colors.length)
            {
                currentColor = 0;
            }

            spotlight.setColor(colors[currentColor]);

        });
    }
}

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


const game = new Phaser.Game(config);

Подготовка ассетов с нормалями

Для работы системы динамического освещения в WebGL-рендерере Phaser необходимы не только обычные текстуры, но и карты нормалей (normal maps). Нормаль определяет, как поверхность объекта взаимодействует со светом, создавая иллюзию объёма.

В методе preload мы загружаем три типа ассетов, обращая внимание на особенности загрузки для освещения:

this.load.image('sonic', 'assets/sprites/sonic_havok_sanity.png');
this.load.image('bg', [ 'assets/textures/gold.png', 'assets/textures/gold-n.png' ]);
this.load.bitmapFont('ice', [ 'assets/fonts/bitmap/iceicebaby.png', 'assets/fonts/bitmap/iceicebaby_n.png' ], 'assets/fonts/bitmap/iceicebaby.xml');

Ключевой момент — передача массивов путей для текстур 'bg' и шрифта 'ice'. Первый элемент массива — это путь к диффузной текстуре (основному цвету), а второй — путь к карте нормалей (суффикс -n). Phaser автоматически связывает эти текстуры для корректного расчёта освещения. Обычный спрайт 'sonic' загружается без нормалей, поэтому на него свет будет падать без учёта рельефа поверхности.

Создание сцены и включение освещения

В методе create мы сначала размещаем фоновый спрайт и персонажа, а затем создаём bitmap-текст. Обратите внимание на цепочки методов для настройки.

this.add.sprite(400, 300, 'bg').setLighting(true).setAlpha(0.2);
this.add.bitmapText(400, 300, 'ice', 'Bitmap Text\nwith Lights', 110).setCenterAlign().setOrigin(0.5).setLighting(true);

Метод setLighting(true) — это самый важный вызов. Он указывает рендереру, что данный игровой объект должен реагировать на динамические источники света в сцене. Без этого вызова объект будет отрисован с использованием только окружающего света.

Далее мы активируем саму систему освещения для сцены:

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

this.lights.enable() включает систему. this.lights.setAmbientColor(0x808080) устанавливает цвет окружающего освещения — в данном случае средний серый (0x808080). Это базовый свет, который равномерно освещает все объекты с включённым lighting, независимо от расположения источников.

Создание и управление динамическим источником света

После включения системы мы создаём основной источник света — прожектор (spotlight).

const spotlight = this.lights.addLight(400, 300, 280).setIntensity(3);

this.lights.addLight(x, y, radius) создаёт точечный источник света с заданными координатами и радиусом влияния (280 пикселей). Метод setIntensity(3) устанавливает интенсивность. Значение больше 1 делает свет ярче стандартного.

Затем мы привязываем этот источник к курсору мыши, обрабатывая события pointermove:

this.input.on('pointermove', pointer => {
    spotlight.x = pointer.x;
    spotlight.y = pointer.y;
});

При каждом движении мыши мы просто обновляем координаты `xиyобъектаspotlight`. Phaser автоматически пересчитывает освещение сцены в следующем кадре, создавая плавный эффект следования света за курсором.

Интерактивная смена цвета света

Чтобы сделать пример более наглядным и интерактивным, добавим возможность менять цвет прожектора по клику мыши. Для этого определим массив цветов в формате HEX.

const colors = [
    0xffffff, 0xff0000, 0x00ff00, 0x00ffff, 0xff00ff, 0xffff00
];

Затем в обработчике события pointerdown мы циклически перебираем этот массив и применяем цвет к источнику света.

this.input.on('pointerdown', pointer => {
    currentColor++;
    if (currentColor === colors.length) {
        currentColor = 0;
    }
    spotlight.setColor(colors[currentColor]);
});

Метод setColor() объекта света мгновенно изменяет его оттенок. Это отличный способ визуальной обратной связи или создания спецэффектов (например, мигания сигнальных огней или изменения настроения сцены).

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

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

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

Если установить type: Phaser.CANVAS, то освещение работать не будет. WebGL-рендерер использует шейдеры для расчёта взаимодействия света, нормалей и диффузных текстур, что позволяет добиться плавной производительности даже при нескольких движущихся источниках света.

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

Система освещения Phaser 3 — это гибкий инструмент, который превращает плоскую 2D-графику в нечто объёмное и живое. В этом примере мы увидели, как легко заставить свет следовать за курсором и менять цвет по клику, взаимодействуя с bitmap-текстом и текстурой с нормалями. Для экспериментов попробуйте: добавить несколько источников света с разными радиусами и интенсивностью, привязать их к движущимся спрайтам (например, к факелу в руке персонажа), создать эффект мерцания с помощью таймера или использовать освещение для подсветки только определённых зон игры, создавая атмосферу тайны.