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

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

Версия 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('grad', 'assets/skies/fire.png');
    }

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

        this.add.noise({
            noiseRandomNormal: true
        }, 0, 0, width / 16 / 3, height / 16)
        .setRenderToTexture('noise');

        // The texture we'll distort.
        const base = this.add.image(width  * 1 / 6, height / 2, 'grad');
        Phaser.Actions.FitToRegion(base, 0, { x: 0, y: 0, width: width / 3, height: height });

        // Visualize the normal noise.
        // Rendering to texture turns off rendering directly to the canvas.
        const noise = this.add.image(width / 2, height / 2, 'noise');
        Phaser.Actions.FitToRegion(noise, 0, { x: width / 3, y: 0, width: width / 3, height: height });

        // Apply normal noise to the texture.
        const image = this.add.image(width * 5 / 6, height / 2, 'grad');
        Phaser.Actions.FitToRegion(image, 0, { x: width * 2 / 3, y: 0, width: width / 3, height: height });
        image.enableFilters().filters.internal.addDisplacement('noise', 0.1, 0.1);
    }
}

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

const game = new Phaser.Game(config);

Загрузка ресурсов и создание объекта шума

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

this.add.noise({
    noiseRandomNormal: true
}, 0, 0, width / 16 / 3, height / 16)
.setRenderToTexture('noise');

Конфигурационный объект { noiseRandomNormal: true } указывает, что шум должен генерироваться в нормальном (Normal) распределении, что дает более плавные и органичные переходы по сравнению с равномерным. Параметры 0, 0, width / 16 / 3, height / 16 задают позицию (x, y), ширину и высоту генерируемой карты шума. Ширина делится на 48 (width / 16 / 3), а высота — на 16, что создает прямоугольную текстуру. Метод .setRenderToTexture('noise') рендерит этот шум не на канвас напрямую, а в текстуру с именем 'noise', чтобы её можно было повторно использовать.

Подготовка и визуализация исходных элементов

Прежде чем применять шум, нужно подготовить сцену. Создаются три изображения, которые будут размещены горизонтально.

const base = this.add.image(width  * 1 / 6, height / 2, 'grad');
Phaser.Actions.FitToRegion(base, 0, { x: 0, y: 0, width: width / 3, height: height });

Первое изображение base — это оригинальная текстура 'grad', размещенная в левой трети экрана. Phaser.Actions.FitToRegion масштабирует и позиционирует изображение так, чтобы оно заполнило заданную область (регион) с координатами { x: 0, y: 0, width: width / 3, height: height }.

const noise = this.add.image(width / 2, height / 2, 'noise');
Phaser.Actions.FitToRegion(noise, 0, { x: width / 3, y: 0, width: width / 3, height: height });

Второе изображение noise визуализирует саму текстуру шума, которую мы создали на предыдущем шаге. Оно размещается в центральной трети экрана. Это позволяет наглядно увидеть, как выглядит сгенерированный нормальный шум.

Применение шума как карты смещения (Displacement Map)

Самое интересное — использование текстуры шума для искажения другого изображения. Это делается с помощью фильтра смещения (Displacement Filter).

const image = this.add.image(width * 5 / 6, height / 2, 'grad');
Phaser.Actions.FitToRegion(image, 0, { x: width * 2 / 3, y: 0, width: width / 3, height: height });
image.enableFilters().filters.internal.addDisplacement('noise', 0.1, 0.1);

Создается третье изображение image с той же текстурой 'grad' и размещается в правой трети экрана. Метод image.enableFilters() активирует систему фильтров для этого игрового объекта. Затем через внутренний менеджер фильтров filters.internal добавляется фильтр смещения: .addDisplacement('noise', 0.1, 0.1).

Первый аргумент 'noise' — это ключ текстуры, которая будет использоваться как карта смещения (та самая, которую мы создали и сохранили). Второй и третий аргументы (0.1, 0.1) — это множители (scale) смещения по осям X и Y. Они определяют силу искажения. Фильтр берет значения из текстуры шума (где каждый пиксель содержит информацию о смещении) и применяет их к пикселям исходного изображения, создавая эффект волн, ряби или деформации.

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

Код завершается стандартной конфигурацией игры Phaser.

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

const game = new Phaser.Game(config);

Важно указать type: Phaser.WEBGL, так как фильтры (включая смещение) требуют WebGL-рендерера. Параметры width и height задают разрешение игрового поля. Класс сцены Example передается в конфигурацию, и игра инициализируется созданием экземпляра Phaser.Game.

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

Объект Noise в Phaser — это не просто визуализация шума, а полноценный источник данных для постобработки. Используя его как карту смещения, вы можете легко создавать сложные эффекты искажения для воды, магии, теплового марева или повреждений. Для экспериментов попробуйте: изменить параметры noiseRandomNormal на false для равномерного шума; увеличить множители в .addDisplacement() для более сильного искажения; анимировать позицию или масштаб объекта шума для создания движущихся волн; или применить шум к спрайтам анимации для эффекта "растворения".