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

Генерация процедурного контента — ключ к созданию уникальных и бесконечных миров. Phaser предоставляет мощный инструмент `add.noisesimplex2d` для работы с симплексным шумом, но его настоящая сила раскрывается в параметрах искажения (warp). Эта статья покажет, как, манипулируя всего несколькими свойствами, вы можете кардинально менять характер процедурной текстуры — от быстрой детализации до гипнотического потока. Эти техники полезны для создания фонов, текстур ландшафта, магических эффектов или динамичных небес.

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

Живой запуск

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

Исходный код


class Example extends Phaser.Scene
{
    noise1;
    noise2;
    noise3;
    noise4;

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

        // Four variants, differing only in noise warp adjustments.

        this.noise1 = this.add.noisesimplex2d({
            noiseCells: [ 4, 9 ],
            noiseWarpAmount: 0.5,
            noiseWarpIterations: 2
        }, width * 1 / 8, height / 2, width / 4, height);

        this.noise2 = this.add.noisesimplex2d({
            noiseCells: [ 4, 9 ],
            noiseWarpAmount: 0.5,
            noiseWarpIterations: 2,
            noiseWarpDetailPower: 4 // Increase detail quickly.
        }, width * 3 / 8, height / 2, width / 4, height);

        this.noise3 = this.add.noisesimplex2d({
            noiseCells: [ 4, 9 ],
            noiseWarpAmount: 0.5,
            noiseWarpIterations: 2,
            noiseWarpContributionPower: 1 // Subsequent octaves decay slower.
        }, width * 5 / 8, height / 2, width / 4, height);

        this.noise4 = this.add.noisesimplex2d({
            noiseCells: [ 4, 9 ],
            noiseWarpAmount: 0.5,
            noiseWarpIterations: 2,
            noiseWarpFlowPower: 8 // Subsequent octaves flow faster
        }, width * 7 / 8, height / 2, width / 4, height);

        this.add.text(10, 10, 'Default warp', { fontSize: 24 }).setStroke('#ff8844', 2).setShadow(2, 2, '#333333', 2, true, false);
        this.add.text(width / 4 + 10, 10, 'Detail +', { fontSize: 24 }).setStroke('#ff8844', 2).setShadow(2, 2, '#333333', 2, true, false);
        this.add.text(width / 2 + 10, 10, 'Contribution decay -', { fontSize: 24 }).setStroke('#ff8844', 2).setShadow(2, 2, '#333333', 2, true, false);
        this.add.text(width * 3 / 4 + 10, 10, 'Flow +', { fontSize: 24 }).setStroke('#ff8844', 2).setShadow(2, 2, '#333333', 2, true, false);
    }

    update (time)
    {
        const t = (time / 1000) % (Math.PI * 2);

        this.noise1.noiseFlow = t;
        this.noise2.noiseFlow = t;
        this.noise3.noiseFlow = t;
        this.noise4.noiseFlow = t;
    }
}

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

const game = new Phaser.Game(config);

Основа: создание объекта шума

Класс Example расширяет Phaser.Scene. В методе create мы создаем четыре варианта симплексного шума, используя метод this.add.noisesimplex2d. Каждый объект размещается на своей позиции по горизонтали и имеет одинаковый базовый размер.

this.noise1 = this.add.noisesimplex2d({
    noiseCells: [ 4, 9 ],
    noiseWarpAmount: 0.5,
    noiseWarpIterations: 2
}, width * 1 / 8, height / 2, width / 4, height);

Здесь noiseCells задает количество ячеек шума по осям X и Y (влияет на масштаб паттерна). noiseWarpAmount определяет силу искажения, а noiseWarpIterations — количество октав (слоев) шума, которые будут наложены. Второй и последующие аргументы задают позицию X, Y, ширину и высоту отображаемой области объекта.

Ускоряем детализацию: noiseWarpDetailPower

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

this.noise2 = this.add.noisesimplex2d({
    noiseCells: [ 4, 9 ],
    noiseWarpAmount: 0.5,
    noiseWarpIterations: 2,
    noiseWarpDetailPower: 4 // Увеличиваем детализацию быстрее.
}, width * 3 / 8, height / 2, width / 4, height);

Чем выше значение noiseWarpDetailPower, тем более "колючим" и высокочастотным становится шум с каждым новым слоем. Это полезно для имитации сложных, изрезанных поверхностей вроде скал или коры дерева.

Контролируем вклад октав: noiseWarpContributionPower

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

this.noise3 = this.add.noisesimplex2d({
    noiseCells: [ 4, 9 ],
    noiseWarpAmount: 0.5,
    noiseWarpIterations: 2,
    noiseWarpContributionPower: 1 // Последующие октавы затухают медленнее.
}, width * 5 / 8, height / 2, width / 4, height);

Значение 1 (меньше стандартного) означает более медленное затухание. В результате высокочастотные октавы вносят больший вклад, создавая более "зернистую" и контрастную текстуру с выраженными мелкими деталями.

Запускаем поток: noiseWarpFlowPower

Четвертый объект настраивается через noiseWarpFlowPower. Этот параметр определяет, насколько меняется смещение (noiseFlow) для каждой последующей октавы искажения.

this.noise4 = this.add.noisesimplex2d({
    noiseCells: [ 4, 9 ],
    noiseWarpAmount: 0.5,
    noiseWarpIterations: 2,
    noiseWarpFlowPower: 8 // Последующие октавы "текут" быстрее.
}, width * 7 / 8, height / 2, width / 4, height);

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

Анимация через свойство noiseFlow

Динамика — это жизнь. В методе update мы анимируем все четыре текстуры, плавно изменяя их свойство noiseFlow. Это свойство сдвигает координаты выборки шума, создавая иллюзию плавного движения паттерна.

update (time)
{
    const t = (time / 1000) % (Math.PI * 2);

    this.noise1.noiseFlow = t;
    this.noise2.noiseFlow = t;
    this.noise3.noiseFlow = t;
    this.noise4.noiseFlow = t;
}

Мы берем время в миллисекундах, преобразуем его в секунды и ограничиваем диапазон циклической функцией. Полученное значение `t` плавно нарастает от 0 до ~6.28 и сбрасывается, обеспечивая бесшовную циклическую анимацию для каждого объекта шума.

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

Параметры искажения в add.noisesimplex2d — это тонкие кисти для скульптурирования процедурных текстур. Комбинируя noiseWarpDetailPower, noiseWarpContributionPower и noiseWarpFlowPower, вы получаете прямой контроль над детализацией, контрастом и динамикой шума. Для экспериментов попробуйте

  1. Создать текстуру планеты, комбинируя разные значения DetailPower для материков и океанов
  2. Анимировать noiseWarpAmount во времени для эффекта "нарастания" или "успокоения" энергии
  3. Использовать шум в качестве маски для цвета или альфа-канала частиц, чтобы создать сложные динамические эффекты