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

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

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

Живой запуск

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

Исходный код


class Example extends Phaser.Scene
{
    constructor ()
    {
        super();
    }

    preload ()
    {
        this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
        this.load.image('bg', 'assets/textures/gold.png');
    }

    create ()
    {
        this.add.image(400, 300, 'bg');
        this.add.text(10, 10, 'Wheel: Hue\nW + S: Attenuation');

        let colorIndex = 0;
        const spectrum = Phaser.Display.Color.ColorSpectrum(128);

        this.spotlight = this.add.pointlight(400, 300, 0, 128, 1);

        let color = spectrum[colorIndex];

        this.spotlight.color.setTo(color.r, color.g, color.b);

        colorIndex++;

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

            this.spotlight.x = pointer.worldX;
            this.spotlight.y = pointer.worldY;

        });

        this.input.on('wheel', (pointer, over, deltaX, deltaY, deltaZ) => {

            if (deltaY < 0)
            {
                colorIndex--;
            }
            else if (deltaY > 0)
            {
                colorIndex++;
            }

            if (colorIndex === spectrum.length)
            {
                colorIndex = 0;
            }
            else if (colorIndex < 0)
            {
                colorIndex = spectrum.length - 1;
            }

            color = spectrum[colorIndex];

            this.spotlight.color.setTo(color.r, color.g, color.b);

        });

        this.input.keyboard.on('keydown-W', () => {

            this.spotlight.attenuation += 0.01;

        });

        this.input.keyboard.on('keydown-S', () => {

            this.spotlight.attenuation -= 0.01;

        });

        const cursors = this.input.keyboard.createCursorKeys();

        const controlConfig = {
            camera: this.cameras.main,
            left: cursors.left,
            right: cursors.right,
            up: cursors.up,
            down: cursors.down,
            zoomIn: this.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.Q),
            zoomOut: this.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.E),
            acceleration: 0.06,
            drag: 0.0005,
            maxSpeed: 1.0
        };

        this.controls = new Phaser.Cameras.Controls.SmoothedKeyControl(controlConfig);
    }

    update (time, delta)
    {
        this.controls.update(delta);

        //  128 is the size we wish to keep the light at, no matter what the camera zoom level is
        this.spotlight.radius = (1 / this.cameras.main.zoom) * 128;
    }
}

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

const game = new Phaser.Game(config);

Создание и базовая настройка света

В Phaser 3 источник точечного света создается с помощью метода this.add.pointlight(). Этот метод возвращает объект типа PointLight, который можно настраивать.

this.spotlight = this.add.pointlight(400, 300, 0, 128, 1);

Первые два аргумента (400, 300) — это начальные координаты X и Y источника света на сцене. Третий аргумент (0) — это цвет в формате целого числа, но в нашем примере он будет переопределен позже. Четвертый аргумент (128) — начальный радиус освещения. Пятый аргумент (1) — начальная интенсивность (attenuation).

Цвет задается отдельно через свойство .color объекта света. В примере используется цветовой спектр, сгенерированный вспомогательной функцией Phaser.Display.Color.ColorSpectrum(128), которая создает массив из 128 цветовых значений.

const spectrum = Phaser.Display.Color.ColorSpectrum(128);
let color = spectrum[colorIndex];
this.spotlight.color.setTo(color.r, color.g, color.b);

Интерактивное управление: мышь и клавиатура

Пример связывает управление светом с действиями пользователя. Положение источника света привязано к курсору мыши с помощью события 'pointermove'.

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

Свойства pointer.worldX и pointer.worldY предоставляют координаты курсора в мировой системе координат сцены, что гарантирует корректную работу при движении камеры.

Цикл изменения цвета (Hue) завязан на колесико мыши ('wheel'). Событие передает deltaY, указывающую направление прокрутки. В зависимости от этого значения индекс цвета в массиве spectrum увеличивается или уменьшается, обеспечивая плавный переход по всему спектру.

if (deltaY < 0) {
    colorIndex--;
} else if (deltaY > 0) {
    colorIndex++;
}
// ... обработка границ массива
color = spectrum[colorIndex];
this.spotlight.color.setTo(color.r, color.g, color.b);

Интенсивность света (затухание, attenuation) изменяется клавишами W и S. Чем выше значение attenuation, тем быстрее свет затухает с расстоянием, создавая более резкую границу светового пятна.

this.input.keyboard.on('keydown-W', () => {
    this.spotlight.attenuation += 0.01;
});
this.input.keyboard.on('keydown-S', () => {
    this.spotlight.attenuation -= 0.01;
});

Управление камерой и синхронизация света

Для навигации по большой сцене в примере используется Phaser.Cameras.Controls.SmoothedKeyControl. Это готовый контроллер, который обеспечивает плавное перемещение и зум камеры с ускорением и инерцией.

const controlConfig = {
    camera: this.cameras.main,
    left: cursors.left,
    right: cursors.right,
    up: cursors.up,
    down: cursors.down,
    zoomIn: this.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.Q),
    zoomOut: this.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.E),
    acceleration: 0.06,
    drag: 0.0005,
    maxSpeed: 1.0
};
this.controls = new Phaser.Cameras.Controls.SmoothedKeyControl(controlConfig);

Контроллер обновляется в каждом кадре в методе update().

Самая важная часть синхронизации — поддержание визуального размера светового радиуса независимо от зума камеры. Без этого при приближении камеры (zoom > 1) свет будет занимать все меньше экранного пространства, а при отдалении (zoom < 1) — неограниченно расти.

update (time, delta) {
    this.controls.update(delta);
    this.spotlight.radius = (1 / this.cameras.main.zoom) * 128;
}

Формула (1 / this.cameras.main.zoom) * 128 компенсирует зум. Если камера приближена в 2 раза (zoom = 2), радиус света будет установлен в (1/2)*128 = 64, чтобы видимый размер остался примерно равным исходным 128 пикселям.

Конфигурация игры и настройка сцены

Классическая конфигурация игры (config) определяет базовые параметры, такие как размер холста, цвет фона и корневой сценой является наш класс Example.

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

Обратите внимание на черный цвет фона ('#000000'). На темном фоне световые эффекты выглядят наиболее контрастно и эффектно. В методе preload() загружается текстурный фон ('bg'), на котором будут видны световые блики. Загрузка настроена на использование публичного репозитория с примерами Phaser.

preload () {
    this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
    this.load.image('bg', 'assets/textures/gold.png');
}
create () {
    this.add.image(400, 300, 'bg');
    // ... остальной код создания
}

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

Точечные источники света в Phaser 3 — это гибкий инструмент для создания атмосферы и интерактивных механик. Вы можете использовать их для подсветки персонажа, создания фонарика, реализации "зрения" для врагов или просто для украшения интерфейса. Для экспериментов попробуйте: изменить формулу расчета радиуса для другого визуального эффекта; добавить несколько источников света с разными цветами; привязать свет не к курсору, а к спрайту игрока; использовать маску (LightLayer имеет метод setMask) для создания сложных форм освещения, например, через щель в двери.