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

Эффекты освещения могут преобразить вашу игру, добавив атмосферности и глубины. В Phaser 3 система освещения позволяет легко создавать динамические световые сцены. В этой статье мы разберем практический пример, где источник света привязан к движению указателя мыши, а также добавлены статические и анимированные огни. Вы узнаете, как включать освещение для спрайтов, управлять его параметрами и создавать простые, но впечатляющие визуальные эффекты.

Версия 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('phaser', 'assets/sprites/phaser2.png');
    }

    create ()
    {
        const brick = this.add.sprite(0, 0, 'brick');
        brick.setOrigin(0, 0);
        brick.setLighting(true);

        this.light = this.lights.addLight(0, 0, 200).setScrollFactor(0).setIntensity(2);

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

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

            this.light.x = pointer.x;
            this.light.y = pointer.y;

        });

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

            if (this.lights.active)
            {
                this.lights.disable();
            }
            else
            {
                this.lights.enable();
            }

        });

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

        this.lights.addLight(0, 100, 100).setColor(0xff0000).setIntensity(3.0);
        this.lights.addLight(0, 200, 100).setColor(0x00ff00).setIntensity(3.0);
        this.lights.addLight(0, 300, 100).setColor(0x0000ff).setIntensity(3.0);
        this.lights.addLight(0, 400, 100).setColor(0xffff00).setIntensity(3.0);

        this.offsets = [ Math.random() + 1 - 2, Math.random() + 1 - 2, Math.random() + 1 - 2, Math.random() + 1 - 2 ];

        this.time = 0;
    }

    update ()
    {
        let index = 0;

        this.lights.lights.forEach(currLight =>
        {
            if (this.light !== currLight)
            {
                currLight.x = 400 + Math.sin(this.offsets[index]) * 1000;
                this.offsets[index] += 0.02;
                index += 1;
            }
        });

        this.cameras.main.scrollX = Math.sin(this.time) * 400;

        this.time += 0.01;
    }
}

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


const game = new Phaser.Game(config);

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

В методе 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('phaser', 'assets/sprites/phaser2.png');

Создание основного света и его привязка к курсору

В методе create мы создаем фон из спрайта кирпича и активируем для него систему освещения с помощью метода setLighting(true). Без этого вызова свет на спрайте не будет отображаться.

Затем создается главный источник света с помощью this.lights.addLight(0, 0, 200). Первые два аргумента — это начальные координаты X и Y, третий — радиус света. Метод setScrollFactor(0) фиксирует свет относительно камеры (он не будет двигаться при скролле), а setIntensity(2) задает его интенсивность.

Ключевой момент — привязка координат света к положению указателя мыши. Для этого мы подписываемся на событие pointermove.

const brick = this.add.sprite(0, 0, 'brick');
brick.setOrigin(0, 0);
brick.setLighting(true);

this.light = this.lights.addLight(0, 0, 200).setScrollFactor(0).setIntensity(2);

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

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

Управление системой освещения и добавление статических огней

В примере реализовано простое переключение всей системы освещения по клику мыши. При событии pointerdown проверяется состояние this.lights.active, и система либо отключается (disable()), либо включается (enable()).

Далее мы добавляем спрайт логотипа Phaser, который также будет реагировать на свет, и создаем четыре стационарных источника света разного цвета с высокой интенсивностью. Цвет задается методом setColor() в шестнадцатеричном формате.

this.input.on('pointerdown', pointer => {
    if (this.lights.active)
    {
        this.lights.disable();
    }
    else
    {
        this.lights.enable();
    }
});

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

this.lights.addLight(0, 100, 100).setColor(0xff0000).setIntensity(3.0);
this.lights.addLight(0, 200, 100).setColor(0x00ff00).setIntensity(3.0);
this.lights.addLight(0, 300, 100).setColor(0x0000ff).setIntensity(3.0);
this.lights.addLight(0, 400, 100).setColor(0xffff00).setIntensity(3.0);

Анимация и движение в игровом цикле

В методе update происходит вся анимация. Мы проходим по всем созданным источникам света в массиве this.lights.lights, кроме главного (привязанного к курсору). Для каждого из четырех цветных огней мы меняем координату X по синусоидальному закону, используя заранее сгенерированные случайные смещения (offsets). Это создает эффект плавающего, хаотичного движения огоньков.

Отдельно анимируется скроллинг основной камеры по оси X, также с помощью синуса. Это демонстрирует, как свет, созданный с setScrollFactor(0), остается на месте, в то время как мир с текстурами и другими огнями движется.

update ()
{
    let index = 0;
    this.lights.lights.forEach(currLight =>
    {
        if (this.light !== currLight)
        {
            currLight.x = 400 + Math.sin(this.offsets[index]) * 1000;
            this.offsets[index] += 0.02;
            index += 1;
        }
    });
    this.cameras.main.scrollX = Math.sin(this.time) * 400;
    this.time += 0.01;
}

Важные настройки конфигурации

Для работы системы освещения в Phaser 3 критически важно указать правильный тип рендерера. Освещение работает только в контексте WebGL. Это задается в объекте конфигурации игры полем type: Phaser.WEBGL. Если указать Phaser.CANVAS, освещение не будет функционировать.

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

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

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