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

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

Версия Phaser: код и демо в этой статье рассчитаны на Phaser 3.90.0.

Живой запуск

Ниже встроен рабочий билд примера. Оригинальный источник: GitHub.

Исходный код


var config = {
    type: Phaser.WEBGL,
    parent: 'phaser-example',
    width: 800,
    height: 600,
    scene: {
        preload: preload,
        create: create,
        update: update
    }
};


var game = new Phaser.Game(config);

var tilesprite;

function 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']);
}

function create ()
{
    tilesprite = this.add.tileSprite(400, 300, 800, 600, 'brick').setPipeline('Light2D');

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

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

    this.input.on('pointermove', function (pointer) {

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

    });

    var i = 0;

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

        if (i === 1) {
            //this.lights.enable();
            this.lights.active = true;
            i = 0;
        } else {
            //this.lights.disable();
            this.lights.active = false;
            i = 1;
        }
    });
}

function update ()
{
    tilesprite.tilePositionX += 0.3;
    tilesprite.tilePositionY += 0.6;
}

Настройка сцены и загрузка текстур с картами нормалей

Пример начинается с базовой конфигурации игры. Ключевой момент — использование Phaser.WEBGL, так как система освещения работает только с этим рендерером.

var config = {
    type: Phaser.WEBGL,
    parent: 'phaser-example',
    width: 800,
    height: 600,
    scene: {
        preload: preload,
        create: create,
        update: update
    }
};
var game = new Phaser.Game(config);

В методе preload загружается не обычное изображение, а набор из цветовой текстуры и карты нормалей. Карта нормалей (файл с суффиксом _n) необходима для корректного расчёта того, как свет падает на поверхность, создавая иллюзию объёма.

function 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']);
}

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

В фазе create мы создаём фон с помощью TileSprite. Важный шаг — установка пайплайна рендеринга Light2D для этого спрайта через метод .setPipeline('Light2D'). Без этого свет не будет влиять на объект.

tilesprite = this.add.tileSprite(400, 300, 800, 600, 'brick').setPipeline('Light2D');

Далее система освещения активируется методом this.lights.enable(). Метод this.lights.setAmbientColor(0x808080) устанавливает цвет окружающего (фонового) света. Серый цвет (0x808080) означает, что без активных источников объекты будут отображаться в половинной яркости.

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

Добавление интерактивного источника света

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

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

Чтобы свет следовал за курсором мыши, мы подписываемся на событие pointermove. При каждом движении мыши координаты прожектора обновляются.

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

Управление видимостью света по клику

Пример добавляет возможность включать и выключать все динамические источники света по клику мыши, оставляя только ambient-освещение. Для этого используется флаг this.lights.active. Обратите внимание, что это не то же самое, что this.lights.disable(), который отключает всю систему.

var i = 0;
this.input.on('pointerdown', (pointer) => {
    if (i === 1) {
        this.lights.active = true; // Включаем динамические источники света
        i = 0;
    } else {
        this.lights.active = false; // Выключаем динамические источники света
        i = 1;
    }
});

Анимация фона для усиления эффекта

Чтобы продемонстрировать, как освещение работает с движущимися объектами, в функции update анимируется текстура фона. Изменение свойств tilePosition заставляет текстуру кирпича медленно смещаться, создавая иллюзию движения поверхности под динамическим светом.

function update ()
{
    tilesprite.tilePositionX += 0.3;
    tilesprite.tilePositionY += 0.6;
}

Это наглядно показывает, что освещение пересчитывается каждый кадр и корректно взаимодействует с анимированной геометрией.

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

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