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

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

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

Живой запуск

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

Исходный код


class Example extends Phaser.Scene
{
    image;
    vignette;

    preload ()
    {
        this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
        this.load.image('anime-market', 'assets/pics/anime-market.png');
    }

    create ()
    {
        this.image = this.add.image(400, 300, 'anime-market').setScale(2);

        this.vignette = this.cameras.main.filters.external.addVignette(0.5, 0.37, 1, 0.25);
        this.vignette.color.setTo(127, 255, 255);
    }

    update (time, delta)
    {
        this.image.x = 16 * Math.sin(time / 765) + 640;
        this.image.y = 16 * Math.sin(time / 1000) + 512;
        this.image.rotation = 0.005 * Math.sin(time / 881);
        this.vignette.strength = 0.1 + 0.05 * Math.sin(time / 384);
    }
}

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

let game = new Phaser.Game(config);

Инициализация сцены и загрузка изображения

В Phaser вся игровая логика организуется в сценах. В данном примере мы создаём класс Example, который наследуется от Phaser.Scene. Метод preload() используется для предварительной загрузки ресурсов. Здесь мы загружаем одно фоновое изображение.

preload ()
{
    this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
    this.load.image('anime-market', 'assets/pics/anime-market.png');
}

Метод setBaseURL() задаёт базовый путь для загрузчика, а load.image() регистрирует изображение с ключом 'anime-market'.

Создание изображения и добавление фильтра виньетирования

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

create ()
{
    this.image = this.add.image(400, 300, 'anime-market').setScale(2);

    this.vignette = this.cameras.main.filters.external.addVignette(0.5, 0.37, 1, 0.25);
    this.vignette.color.setTo(127, 255, 255);
}

Ключевой момент — обращение к менеджеру внешних фильтров основной камеры через this.cameras.main.filters.external. Метод addVignette() создаёт и добавляет фильтр, принимая параметры: `x(горизонтальный центр),y(вертикальный центр),radius(радиус внутренней, не затемнённой области) иstrength(сила затемнения). Возвращённый объект фильтра сохраняется в свойствеthis.vignetteдля дальнейшего управления. Сразу же меняем цвет виньетирования с помощьюthis.vignette.color.setTo()`. По умолчанию цвет чёрный, здесь же задаётся светло-голубой оттенок.

Анимация изображения и силы виньетирования

Метод update() вызывается на каждом кадре игры. В нём мы анимируем положение, поворот фонового изображения и силу эффекта виньетирования, создавая живое, дышащее движение.

update (time, delta)
{
    this.image.x = 16 * Math.sin(time / 765) + 640;
    this.image.y = 16 * Math.sin(time / 1000) + 512;
    this.image.rotation = 0.005 * Math.sin(time / 881);
    this.vignette.strength = 0.1 + 0.05 * Math.sin(time / 384);
}

Параметр time — это время в миллисекундах с момента старта игры. Используя функцию Math.sin(), мы получаем плавные колебания. Координаты `xиyизображения, а также егоrotation(вращение) меняются по синусоидальному закону с разной частотой, что делает движение менее механическим. Сила виньетированияthis.vignette.strength` также пульсирует, периодически усиливая и ослабляя эффект. Это свойство доступно для изменения после создания фильтра.

Конфигурация игры

Для запуска игры необходимо создать её экземпляр с конфигурационным объектом. В нём указывается тип рендерера, размеры холста, цвет фона, родительский HTML-элемент и стартовая сцена.

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

let game = new Phaser.Game(config);

Phaser.AUTO автоматически выбирает между WebGL и Canvas. Размеры width и height задают разрешение игрового мира. Свойство backgroundColor определяет цвет, который будет виден за пределами изображения. Параметр parent — это ID HTML-элемента, в который будет встроен canvas. scene принимает класс сцены, которая будет запущена первой.

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

Фильтр виньетирования в Phaser 3 — это мощный и простой инструмент для создания атмосферных эффектов и управления вниманием игрока. Как мы увидели, его можно динамически контролировать прямо во время выполнения игры. Для экспериментов попробуйте связать силу виньетирования со здоровьем персонажа, менять цвет в зависимости от времени суток в игре или привязать центр эффекта `xиy` к положению курсора мыши, создавая эффект фонарика.