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

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

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

Живой запуск

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

Исходный код


class Example extends Phaser.Scene
{
    noise1;
    noise2;
    noise3;
    tileSprite;
    acc = 0;

    create()
    {
        const { width, height } = this.scale;

        this.noise1 = this.add.noise({}, width / 2, height / 5, width, height / 4);

        this.noise2 = this.add.noise({}, width / 2, height / 2, width, height / 4);

        this.noise3 = this.add.noise({}, width / 2, height * 4 / 5, width, height / 4);

        this.noise3.setRenderToTexture('noise3');

        this.tileSprite = this.add.tileSprite(width / 2, height * 4 / 5, width, height / 4, 'noise3');
    }

    update (time)
    {
        // Unaligned offset creates random noise, even when extremely small.
        const t = time / 1000000;
        this.noise1.noiseOffset = [t, 0];

        this.acc++;

        // Offset that matches noise resolution is more stable, but can flicker.
        this.noise2.noiseOffset = [this.acc / this.scale.width, 0];

        // Using noise as a texture in a TileSprite is smooth.
        // Note that `noise3` re-renders the same image every frame; this could be optimized.
        this.tileSprite.setTilePosition(this.acc, 0);
    }
}

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

const game = new Phaser.Game(config);

Инициализация объектов шума

В методе create() сцены создаются три основных объекта. Ключевой метод — this.add.noise(), который создает игровой объект, отображающий шум.

const { width, height } = this.scale;

this.noise1 = this.add.noise({}, width / 2, height / 5, width, height / 4);
this.noise2 = this.add.noise({}, width / 2, height / 2, width, height / 4);
this.noise3 = this.add.noise({}, width / 2, height * 4 / 5, width, height / 4);

Параметры метода: конфигурационный объект (оставлен пустым для значений по умолчанию), позиция X, позиция Y, ширина и высота области отрисовки шума. Все три объекта имеют одинаковый размер, но расположены на разной высоте экрана.

Для третьего объекта выполняется дополнительная операция рендера в текстуру, которая будет использована позже.

this.noise3.setRenderToTexture('noise3');

Анимация через смещение (offset)

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

Первый объект, noise1, использует смещение, основанное на времени. Так как значение time очень большое, его делят на 1 000 000, чтобы смещение менялось плавно.

const t = time / 1000000;
this.noise1.noiseOffset = [t, 0];

Свойство noiseOffset принимает массив из двух чисел, задающих смещение по осям X и Y. Такой подход создает псевдослучайный, но непрерывный скролл.

Второй объект, noise2, использует смещение, привязанное к разрешению текстуры шума и ширине экрана.

this.acc++;
this.noise2.noiseOffset = [this.acc / this.scale.width, 0];

Здесь this.acc инкрементируется каждый кадр. Деление на ширину экрана (this.scale.width) помогает синхронизировать смещение с внутренней сеткой шума. Однако, как указано в комментарии, это может приводить к мерцанию, если смещение слишком резко 'перескакивает' с одной ячейки шума на другую.

Оптимизация через TileSprite и текстуру

Третий подход решает проблему мерцания и потенциально более производителен. Вместо того чтобы пересчитывать шум каждый кадр, мы один раз рендерим его в текстуру и затем анимируем спрайт, который ее использует.

На этапе создания, после рендера noise3 в текстуру с именем 'noise3', мы создаем TileSprite.

this.tileSprite = this.add.tileSprite(width / 2, height * 4 / 5, width, height / 4, 'noise3');

TileSprite — это спрайт, текстура которого может зацикливаться (тилироваться). Мы указываем ему использовать текстуру 'noise3'.

В цикле обновления мы не трогаем объект noise3. Вместо этого мы смещаем позицию тилизации (тайлов) внутри TileSprite.

this.tileSprite.setTilePosition(this.acc, 0);

Это создает идеально плавный и стабильный скролл, потому что анимируется не сама текстура шума (она статична), а координаты ее отображения. Важное замечание из кода: объект noise3 все равно перерисовывается каждый кадр (re-renders the same image every frame), что избыточно. В реальном проекте рендер в текстуру следует выполнить один раз, например, после создания объекта.

Сравнение подходов и их применение

Давайте резюмируем ключевые различия: * **noise1 (Смещение на основе времени)**: Дает органичный, 'природный' вид скролла, хорошо подходит для фонов воды, дыма или облаков. Менее предсказуем. * **noise2 (Дискретное смещение)**: Может создавать стабильные паттерны, но склонен к мерцанию при определенной скорости. Требует тонкой настройки. * **noise3 + TileSprite**: Обеспечивает самый плавный скролл без визуальных артефактов. Это оптимальный выбор для фонов, которые должны равномерно и непрерывно двигаться (например, бегущая дорога, звездное поле).

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

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

Phaser предлагает гибкие инструменты для работы с процедурным шумом: от прямого управления noiseOffset до оптимизированного скроллинга через TileSprite. Для экспериментов попробуйте: изменить второй параметр в noiseOffset (например, [t, t]) для диагонального движения; применить цветовые фильтры к объектам noise; или создать многослойный фон, комбинируя несколько типов шума с разной скоростью скролла (параллакс-эффект). Помните, что рендер шума в текстуру в каждом кадре — дорогая операция, и ее стоит кэшировать.