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

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

Версия 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/grass.jpg');
        this.load.atlas('glade', 'assets/atlas/glade.png', 'assets/atlas/glade.json');
    }

    create ()
    {
        this.add.image(400, 300, 'bg').setScale(1.0).setScrollFactor(0, 0);

        const grass = this.add.layer();

        const trees = [ 'Spruce-1', 'Spruce-2', 'Spruce-3', 'Spruce-5', 'Spruce-6', 'Flower_2' ];

        for (let i = 0; i < 128; i++)
        {
            let x = Phaser.Math.Between(0, 800);
            let y = Phaser.Math.Between(100, 600 * 4);

            let frame = Phaser.Utils.Array.GetRandom(trees);

            let tree = this.add.image(x, y, 'glade', frame);

            tree.setDepth(y);
            tree.setOrigin(0.5, 1);

            grass.add(tree);
        }

        const camera = this.cameras.main;

        camera.filters.external.addVignette(0.5, 0.5, 0.7);

        this.tweens.add({
            targets: camera,
            scrollY: 1800,
            duration: 20000,
            yoyo: true,
            loop: -1
        });
    }
}

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

let game = new Phaser.Game(config);

Подготовка ресурсов и создание слоя

В методе preload загружаются необходимые ассеты: фоновая текстура травы и атлас спрайтов с деревьями и цветами. Обратите внимание, что setBaseURL задаёт базовый путь для всех последующих загрузок.

В create первым делом добавляется фоновое изображение. Ключевой момент — вызов setScrollFactor(0, 0). Это фиксирует фон относительно камеры, создавая иллюзию бесконечного заднего плана.

Затем создаётся слой (this.add.layer()). Слои в Phaser — это удобные контейнеры для управления группой объектов. Мы будем добавлять в него деревья.

this.add.image(400, 300, 'bg').setScale(1.0).setScrollFactor(0, 0);
const grass = this.add.layer();

Генерация леса с параллаксом

Далее в цикле генерируется 128 случайных деревьев. Для каждого дерева выбираются случайные координаты `xиy. Важно, что координатаy` может быть значительно больше высоты экрана (до 2400 пикселей), что позволит камере прокручиваться вверх и вниз.

Ключевой приём для создания иллюзии глубины — установка глубины объекта (setDepth) равной его координате `y. Это стандартный подход для 2.5D или изометрических сцен: чем выше объект на экране (большеy), тем дальше он должен казаться, а значит, должен быть отрисован позади объектов с меньшимy`.

setOrigin(0.5, 1) устанавливает точку привязки спрайта в его нижней середине. Это нужно, чтобы деревья "стояли" на своей координате `y`, а не были привязаны к центру.

После создания объект добавляется в ранее созданный слой grass.

let x = Phaser.Math.Between(0, 800);
let y = Phaser.Math.Between(100, 600 * 4);
let frame = Phaser.Utils.Array.GetRandom(trees);
let tree = this.add.image(x, y, 'glade', frame);
tree.setDepth(y);
tree.setOrigin(0.5, 1);
grass.add(tree);

Добавление фильтра виньетки к камере

Самый важный шаг — применение фильтра. Мы получаем ссылку на основную камеру сцены через this.cameras.main. У камеры есть свойство filters, содержащее коллекцию external. Именно в неё мы можем добавлять готовые визуальные эффекты.

Метод addVignette принимает три параметра: позиция `x, позицияyи сила эффектаradius. Все значения от 0 до 1. В примереaddVignette(0.5, 0.5, 0.7)` создаёт виньетку, центрированную по камере, с довольно сильным затемнением по краям. Этот эффект применяется ко всему, что видит камера, включая фон и все объекты на слое.

const camera = this.cameras.main;
camera.filters.external.addVignette(0.5, 0.5, 0.7);

Анимация прокрутки камеры

Чтобы продемонстрировать динамичность эффекта, камера приводится в движение. Создаётся твин, который анимирует свойство scrollY камеры от начального значения до 1800 пикселей за 20 секунд.

Параметры yoyo: true и loop: -1 заставляют анимацию проигрываться в прямом и обратном направлении бесконечно. Это создаёт плавную "прогулку" камеры по лесу, а виньетка, оставаясь закреплённой на камере, равномерно затемняет края изображения на всём протяжении движения.

this.tweens.add({
    targets: camera,
    scrollY: 1800,
    duration: 20000,
    yoyo: true,
    loop: -1
});

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

Мы реализовали атмосферную сцену с параллаксом и стилизующим фильтром. Ключевые выводы: фильтры камеры (camera.filters.external) влияют на весь её вид; установка scrollFactor и depth позволяет легко управлять перспективой; встроенные методы вроде addVignette дают быстрый визуальный результат. Для экспериментов попробуйте: изменить параметры виньетки (например, сместить центр `xв 0.2 для асимметрии); добавить другие фильтры изexternal(например,addGlowилиaddShadow); анимировать неscrollY`, а сами параметры фильтра через твин, чтобы виньетка пульсировала или смещалась.