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

Работа с системой освещения в Phaser 3 открывает возможности для создания атмосферных 2D-игр. Однако при добавлении динамического света часто возникает проблема: объекты, расположенные дальше от камеры, могут некорректно перекрывать ближние, нарушая восприятие глубины. Эта статья на практическом примере показывает, как синхронизировать систему глубины (Depth) с динамическим освещением (Light2D pipeline), чтобы свет корректно взаимодействовал со спрайтами в зависимости от их положения по оси Y, создавая убедительную перспективу.

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

Живой запуск

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

Исходный код


class Example extends Phaser.Scene
{
    constructor ()
    {
        super('Example');
        this.character = null;
    }

    preload ()
    {
        this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
        this.load.image('logo', 'assets/sprites/phaser2.png')
        this.load.image('particle', 'assets/particles/yellow.png')
    }

    create ()
    {
        this.lights.enable()
        this.lights.setAmbientColor(0x000000)

        this.cursorKeys = this.input.keyboard.createCursorKeys()

        this.light = this.lights.addLight(0, 0, 300, 0xffffff, 3)
        this.character = this.add.image(500, 600, 'particle')

        const image = this.add.image(500, 500, 'logo')

        this.add.image(500, 300, 'particle')

        image.setDepth(image.y).setPipeline('Light2D')
    }

    update ()
    {
        if (this.light && this.character)
        {
            const { x, y } = this.character
            this.light.setPosition(x, y)
        }

        if (this.character)
        {
            if (this.cursorKeys)
            {
                if (this.cursorKeys.down.isDown) 	this.character.y += 3
                if (this.cursorKeys.up.isDown) 		this.character.y -= 3
                if (this.cursorKeys.left.isDown) 	this.character.x -= 3
                if (this.cursorKeys.right.isDown) this.character.x += 3
            }
            this.character.setDepth(this.character.y)
        }
    }
}

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

const game = new Phaser.Game(config);

Подготовка сцены и включение света

В методе preload загружаются необходимые ресурсы: логотип Phaser и текстура частицы. Основная настройка происходит в create.

Здесь активируется система освещения сцены и устанавливается цвет окружающего (фонового) света. Важно: установка ambient color в 0x000000 (черный) означает, что без активных источников света вся сцена будет погружена в темноту.

Также создается управляемый источник света и несколько спрайтов.

this.lights.enable()
this.lights.setAmbientColor(0x000000)

this.light = this.lights.addLight(0, 0, 300, 0xffffff, 3)
this.character = this.add.image(500, 600, 'particle')
const image = this.add.image(500, 500, 'logo')
this.add.image(500, 300, 'particle')

Ключевой принцип: связь глубины и позиции Y

Чтобы свет корректно отображался на объектах, их необходимо перевести на специальный конвейер рендеринга Light2D. Это делается методом setPipeline.

Главная "фишка" данного примера — установка глубины спрайта (setDepth) равной его координате Y. Это классический прием для 2D-игр с видом сверху или сбоку: чем больше значение Y (ниже на экране), тем объект считается "ближе" к камере и должен иметь бОльшую глубину, чтобы перекрывать объекты с меньшим Y.

image.setDepth(image.y).setPipeline('Light2D')

Обратите внимание: на другие частицы, созданные в create, конвейер Light2D не назначается. Это значит, что свет на них влиять не будет, и они останутся невидимыми на черном фоне (если не попадут в область освещения).

Динамическое обновление: свет, следующий за персонажем

В методе update реализовано две важные динамические функции.

1. Источник света постоянно следует за спрайтом character. Его позиция обновляется каждый кадр. 2. Спрайт character управляется с помощью стрелок на клавиатуре. При каждом движении его глубина (depth) пересчитывается на основе новой позиции Y.

if (this.light && this.character)
{
    const { x, y } = this.character
    this.light.setPosition(x, y)
}

if (this.cursorKeys.left.isDown) this.character.x -= 3
// ... остальные проверки клавиш

this.character.setDepth(this.character.y)

Таким образом, когда персонаж двигается вниз (увеличивается Y), его глубина растет, и он может визуально перекрывать объекты, находящиеся выше (с меньшим Y), а источник света, следующий за ним, корректно подсвечивает все объекты с учетом их новой иерархии отрисовки.

Настройка игры (Config)

Конфигурация игры стандартна. Ключевой момент для работы данного примера — черный цвет фона (backgroundColor: '#000000'). На темном фоне эффект от системы освещения и глубины виден наиболее четко и контрастно.

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

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

Сочетание системы освещения Light2D и ручного управления глубиной на основе координат — мощный инструмент для создания иллюзии 3D-пространства в 2D-игре. Этот подход идеально подходит для top-down или side-view проектов, где важна перспектива. **Идеи для экспериментов:** 1. Назначьте конвейер Light2D всем спрайтам в сцене и понаблюдайте за разницей. 2. Добавьте несколько статических или движущихся источников света с разным цветом и радиусом. 3. Измените логику расчета глубины, например, используйте setDepth(1000 - this.character.y), чтобы инвертировать порядок отрисовки. 4. Поэкспериментируйте с ненулевым ambientColor, чтобы создать эффект тусклого фонового освещения.