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

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

Версия 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.spritesheet('fish', [ 'assets/sprites/fish-136x80.png', 'assets/sprites/fish-136x80_n.png' ], { frameWidth: 136, frameHeight: 80 });
    }

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

        this.add.particles(0, 0, 'fish', {
            frame: { frames: [ 0, 1, 2 ], cycle: true, quantity: 4 },
            x: -70,
            y: { min: 100, max: 500, steps: 8 },
            lifespan: 5000,
            speedX: { min: 200, max: 400, steps: 8 },
            quantity: 4,
            frequency: 500
        }).setLighting(true);

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

        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);

Загрузка ресурсов с нормальными картами

Освещение в Phaser работает корректно, когда у объектов есть не только цветная текстура (diffuse map), но и карта нормалей (normal map). Карта нормалей определяет, как свет должен отражаться от поверхности, создавая иллюзию объема.

В методе preload мы загружаем три ресурса особым образом: - Спрайт sonic загружается как обычное изображение. - Текстура bg загружается как массив из двух файлов. Phaser автоматически понимает, что первый элемент — цветная текстура, а второй — карта нормалей. - Спрайтшит fish также загружается массивом из двух изображений, где второе — карта нормалей. Параметр frameWidth и frameHeight задает размер одного кадра анимации.

this.load.image('bg', [ 'assets/textures/gold.png', 'assets/textures/gold-n.png' ]);
this.load.spritesheet('fish', [ 'assets/sprites/fish-136x80.png', 'assets/sprites/fish-136x80_n.png' ], { frameWidth: 136, frameHeight: 80 });

Настройка сцены и включение освещения

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

Фон (bg) добавляется первым. Установка альфа-канала на 0.2 делает его полупрозрачным и темным, чтобы световой эффект был более заметным.

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

Далее создается система частиц (particles) на основе спрайтшита fish. Конфигурация определяет, что частицы (рыбки) будут появляться слева за границей экрана (x: -70) на случайной высоте (y: { min: 100, max: 500, steps: 8 }) и двигаться вправо с разной скоростью (speedX). Параметр frame указывает, что для частиц будут циклически использоваться кадры 0, 1 и 2 из спрайтшита. Вызов .setLighting(true) делает каждую частицу восприимчивой к свету.

this.add.particles(0, 0, 'fish', {
    frame: { frames: [ 0, 1, 2 ], cycle: true, quantity: 4 },
    x: -70,
    y: { min: 100, max: 500, steps: 8 },
    lifespan: 5000,
    speedX: { min: 200, max: 400, steps: 8 },
    quantity: 4,
    frequency: 500
}).setLighting(true);

Только после размещения всех «освещаемых» объектов мы включаем систему глобально и настраиваем фоновый (ambient) свет.

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

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

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

Яркость и интерактивность сцене добавляет движущийся точечный источник света — прожектор (spotlight).

Сначала мы создаем свет с помощью this.lights.addLight(), указывая его начальные координаты и радиус воздействия.

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

Здесь 400, 300 — начальные координаты центра света, 280 — его радиус в пикселях. Метод .setIntensity(3) повышает интенсивность свечения в три раза от базовой, делая его более ярким и контрастным.

Затем мы привязываем положение этого света к курсору мыши, обрабатывая событие pointermove.

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

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

Для добавления интерактивности по клику мыши реализована смена цвета прожектора. Цвета хранятся в массиве, а при каждом клике (pointerdown) индекс текущего цвета увеличивается по циклу.

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

Метод spotlight.setColor() мгновенно изменяет цвет испускаемого света.

Конфигурация рендерера и запуск игры

Важнейшее техническое требование для работы освещения — использование WebGL-рендерера. Система освещения Phaser не работает в режиме Canvas (Phaser.CANVAS).

В объекте конфигурации игры необходимо явно указать type: Phaser.WEBGL.

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

const game = new Phaser.Game(config);

Без этой настройки вызовы методов this.lights.enable() и других приведут к ошибке или просто не дадут визуального эффекта.

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

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