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

Эффект "цифрового дождя", известный по фильму "Матрица", — это отличный способ добавить атмосферы и динамики в игру. В Phaser его можно реализовать с помощью мощной системы партиклов, создав не просто хаотичный поток символов, а управляемую, стильную анимацию. Эта статья покажет, как использовать `ParticleEmitter` для генерации сложных эффектов на основе пользовательской логики, что пригодится для создания фонов, визуализаций данных или просто крутых интро.

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

Живой запуск

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

Исходный код


class Example extends Phaser.Scene
{
    preload ()
    {
        this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
        this.load.spritesheet('font', 'assets/fonts/retro/knighthawks-font-filled.png', { frameWidth: 32, frameHeight: 25 });
    }

    create ()
    {
        const codeRain = {
            width: 50,
            height: 40,
            cellWidth: 16,
            cellHeight: 16,
            getPoints: function (quantity)
            {
                const cols = (new Array(codeRain.width)).fill(0);
                const lastCol = cols.length - 1;
                const Between = Phaser.Math.Between;
                const RND = Phaser.Math.RND;
                const points = [];

                for (let i = 0; i < quantity; i++)
                {
                    const col = Between(0, lastCol);
                    let row = (cols[col] += 1);

                    if (RND.frac() < 0.01)
                    {
                        row *= RND.frac();
                    }

                    row %= codeRain.height;
                    row |= 0;

                    points[i] = new Phaser.Math.Vector2(16 * col, 16 * row);
                }

                return points;
            }
        };

        this.add.particles('font').createEmitter({
            alpha: { start: 1, end: 0.25, ease: 'Expo.easeOut' },
            angle: 0,
            blendMode: 'ADD',
            emitZone: { source: codeRain, type: 'edge', quantity: 2000 },
            frame: Phaser.Utils.Array.NumberArray(8, 58),
            frequency: 100,
            lifespan: 6000,
            quantity: 25,
            scale: -0.5,
            tint: 0x0066ff00
        });
    }
}

const config = {
    type: Phaser.WEBGL,
    width: 800,
    height: 600,
    backgroundColor: '#000',
    parent: 'phaser-example',
    scene: Example
};

const game = new Phaser.Game(config);

Загрузка ресурсов и основа эффекта

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

this.load.spritesheet('font', 'assets/fonts/retro/knighthawks-font-filled.png', { frameWidth: 32, frameHeight: 25 });

Ключевая идея эффекта — эмитировать частицы не случайно по всей сцене, а в узлах невидимой сетки, имитирующей падающие столбцы кода. Для этого мы создаём пользовательский объект codeRain, который будет определять эту сетку и точки эмиссии.

Создание пользовательской эмиттерной зоны

Стандартные зоны эмиссии в Phaser (например, RandomZone) разбрасывают частицы случайно. Для контролируемого эффекта дождя нужна своя логика. Объект codeRain описывает сетку и предоставляет метод getPoints для генерации точек.

В его свойствах задаётся размер сетки в "ячейках" и их физические размеры в пикселях.

const codeRain = {
    width: 50,
    height: 40,
    cellWidth: 16,
    cellHeight: 16,
    getPoints: function (quantity) { /* ... */ }
};

Метод getPoints — это сердце эффекта. Он получает запрошенное количество точек (quantity) и возвращает массив векторов Phaser.Math.Vector2 с координатами для новых частиц.

Алгоритм работает так: 1. Создаётся массив cols для отслеживания текущей "высоты" в каждом столбце сетки. 2. Для каждой новой точки случайно выбирается столбец. 3. "Высота" (строка) в этом столбце увеличивается на 1, создавая эффект последовательного падения. 4. С небольшим шансом (1%) строка сдвигается случайным образом, добавляя лёгкий хаос. 5. Координаты переводятся из ячеек сетки в пиксели на экране.

const col = Between(0, lastCol);
let row = (cols[col] += 1);
if (RND.frac() < 0.01) { row *= RND.frac(); }
row %= codeRain.height;
row |= 0;
points[i] = new Phaser.Math.Vector2(16 * col, 16 * row);

Настройка эмиттера частиц

Созданный объект codeRain используется как источник (emitZone) для эмиттера частиц. Указывается тип 'edge', что означает использование метода getPoints для получения точек на "границе" зоны.

emitZone: { source: codeRain, type: 'edge', quantity: 2000 }

Параметр quantity в зоне эмиссии (2000) — это общий пул предрассчитанных точек, из которого эмиттер будет брать позиции. Остальные параметры эмиттера управляют визуальным поведением каждой частицы: - frame: задаёт диапазон кадров из спрайтшита, которые будут использоваться как частицы (символы с кодами от 8 до 58). - alpha, lifespan, scale: определяют, как частица появляется, плавно исчезает и уменьшается за время жизни. - frequency и quantity: управляют интенсивностью — каждые 100 мс испускается 25 новых частиц. - tint: окрашивает все частицы в зелёный цвет (0x0066ff00), характерный для "матричного" стиля.

this.add.particles('font').createEmitter({
    alpha: { start: 1, end: 0.25, ease: 'Expo.easeOut' },
    angle: 0,
    blendMode: 'ADD',
    emitZone: { source: codeRain, type: 'edge', quantity: 2000 },
    frame: Phaser.Utils.Array.NumberArray(8, 58),
    frequency: 100,
    lifespan: 6000,
    quantity: 25,
    scale: -0.5,
    tint: 0x0066ff00
});

Почему именно такая конфигурация работает

Комбинация параметров создаёт убедительную иллюзию: - **blendMode: 'ADD'** заставляет яркие зелёные символы "светиться" и сливаться при наложении, что усиливает эффект потока данных. - **scale: -0.5** — отрицательное значение масштаба означает, что частица будет уменьшаться от своего начального размера до половины. Это создаёт перспективу — символы "убегают" вглубь экрана. - **Долгий lifespan (6000 мс) и частый frequency (100 мс)** обеспечивают плавное, непрерывное заполнение экрана частицами без резких пропаданий. - **Пользовательская emitZone** — главный секрет. Без неё мы получили бы просто облако случайных мерцающих символов. Логика getPoints организует их в упорядоченные, но слегка "сломанные" столбцы, что и является визуальной отсылкой к цифровому дождю.

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

Вы создали не просто эмиттер частиц, а систему, где каждая новая частица появляется в контексте общего алгоритма — это мощный подход. Для экспериментов попробуйте: изменить логику в getPoints на волновую или спиральную; заменить tint на градиент в зависимости от позиции row; использовать другую текстуру (например, простые линии или иконки) для создания эффекта звёздного дождя или водопада из монет. Система частиц Phaser становится по-настоящему гибкой, когда вы управляете источником их появления.