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

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

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

Живой запуск

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

Исходный код


class Example extends Phaser.Scene
{
    noise2d;
    noise3d;

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

        this.noise2d = this.add.noisesimplex2d({
            noiseCells: [ 8, 9 ],
            noiseIterations: 3,
            noiseColorEnd: 0x88ff88
        }, width / 4, height / 2, width / 2, height);

        this.noise3d = this.add.noisesimplex3d({
            noiseCells: [ 8, 9, 4 ],
            noiseIterations: 3,
            noiseColorEnd: 0x8888ff
        }, width * 3 / 4, height / 2, width / 2, height);

        this.add.text(10, 10, '2D', { fontSize: 24 }).setStroke('#ff8844', 2).setShadow(2, 2, '#333333', 2, true, false);
        this.add.text(width / 2 + 10, 10, '3D', { fontSize: 24 }).setStroke('#ff8844', 2).setShadow(2, 2, '#333333', 2, true, false);
    }

    update (time)
    {
        const t = time / 1000;

        this.noise2d.noiseWarpAmount = Math.sin(t) * 0.5 + 0.5;
        this.noise3d.noiseWarpAmount = Math.sin(t) * 0.5 + 0.5;
    }
}

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

const game = new Phaser.Game(config);

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

Класс сцены содержит два ключевых свойства: noise2d и noise3d. Они будут хранить ссылки на наши объекты шума.

В методе create() мы сначала получаем размеры игрового поля через this.scale. Это нужно для корректного позиционирования объектов.

Создание 2D шума происходит с помощью метода this.add.noisesimplex2d(). Первым аргументом передается объект конфигурации, а последующие четыре аргумента задают позицию и размеры объекта (x, y, width, height). Аналогично создается 3D шум через this.add.noisesimplex3d().

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

    this.noise2d = this.add.noisesimplex2d({
        noiseCells: [ 8, 9 ],
        noiseIterations: 3,
        noiseColorEnd: 0x88ff88
    }, width / 4, height / 2, width / 2, height);

    this.noise3d = this.add.noisesimplex3d({
        noiseCells: [ 8, 9, 4 ],
        noiseIterations: 3,
        noiseColorEnd: 0x8888ff
    }, width * 3 / 4, height / 2, width / 2, height);
}

Настройка параметров шума: cells, iterations и color

Конфигурационный объект позволяет тонко настроить вид генерируемой текстуры.

- noiseCells: Массив, определяющий количество "ячеек" шума по каждому измерению. Для 2D шума это [ширина, высота], для 3D — [ширина, высота, глубина]. Меньшие значения дают более крупные и плавные волны, большие — мелкую и частую рябь. В примере 2D шум имеет 8x9 ячеек, а 3D — 8x9x4. - noiseIterations: Количество итераций наложения шума (октав). Чем больше итераций, тем детальнее и сложнее выглядит итоговая текстура. Значение 3 — хороший баланс между производительностью и визуальной насыщенностью. - noiseColorEnd: Конечный цвет градиента, в который окрашивается текстура шума. Начальный цвет — черный (0x000000). 2D шум окрашивается в зеленоватый оттенок (0x88ff88), а 3D — в синеватый (0x8888ff).

// Конфиг для 2D шума
{
    noiseCells: [ 8, 9 ],
    noiseIterations: 3,
    noiseColorEnd: 0x88ff88
}

Динамическая анимация через noiseWarpAmount

Статичная текстура шума — это лишь половина дела. Настоящая магия начинается, когда мы начинаем её искажать. За это отвечает свойство noiseWarpAmount (сила деформации шума), которое есть у обоих объектов.

В методе update(time), который вызывается каждый кадр, мы используем текущее время (time, в миллисекундах) для создания циклической анимации. Время делится на 1000, чтобы получить значение в секундах, которое затем подается на функцию Math.sin(). Синусоида создает плавное oscillating-движение между -1 и 1.

Чтобы преобразовать этот диапазон в удобные для noiseWarpAmount значения от 0 до 1, мы применяем формулу: Math.sin(t) * 0.5 + 0.5. Таким образом, сила деформации будет плавно нарастать от 0 до 1 и обратно до 0, создавая эффект "дыхания" или пульсации текстуры.

update (time) {
    const t = time / 1000;

    this.noise2d.noiseWarpAmount = Math.sin(t) * 0.5 + 0.5;
    this.noise3d.noiseWarpAmount = Math.sin(t) * 0.5 + 0.5;
}

Создание игрового экземпляра (Game Config)

Финальный шаг — создание экземпляра игры Phaser.Game с конфигурацией. Ключевые моменты: - type: Phaser.WEBGL: Обязательно используем WebGL-рендерер, так как объекты шума работают только в этом режиме. - В свойствах width и height задается разрешение игрового поля. - scene: Example: Указываем наш класс сцены, который будет запущен сразу при старте игры.

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

const game = new Phaser.Game(config);

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

Используя всего несколько строчек кода, мы создали две живые, пульсирующие текстуры шума, которые могут служить отличной основой для визуальных эффектов в вашей игре. Главный механизм анимации — циклическое изменение свойства noiseWarpAmount. Идеи для экспериментов: 1. Привяжите noiseWarpAmount не ко времени, а к игровым событиям (например, силе магии персонажа или уровню помех на радаре). 2. Используйте текстуру шума в качестве маски (setMask) для других объектов, чтобы создавать эффекты растворения или энергетических барьеров. 3. Поэкспериментируйте с параметрами noiseCells и noiseIterations для получения совершенно разных визуальных стилей — от гигантских водяных волн до мелкой металлической крошки.