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

Плагины в Phaser — мощный инструмент для расширения функциональности игровых сцен. Они позволяют инкапсулировать сложную логику, например, генерацию процедурной графики, и повторно использовать её в разных проектах. В этой статье мы разберем, как создать Scene Plugin для рисования фрактального множества Жюлиа прямо на Canvas текстуре и встроить его в игру. Этот подход полезен для создания динамических фонов, визуальных эффектов или даже целых мини-игр, основанных на алгоритмической графике.

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

Живой запуск

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

Исходный код


class FractalPlugin extends Phaser.Plugins.ScenePlugin {

    constructor (scene, pluginManager)
    {
        super(scene, pluginManager);

        this.texture;
        this.canvas;
        this.context;
        this.maxIterations = 100;
    }

    create (x, y, w, h)
    {
        this.texture = this.scene.textures.createCanvas('FractalPlugin', w, h);

        this.canvas = this.texture.canvas;
        this.context = this.texture.context;

        this.drawJulia();

        this.texture.refresh();

        let image = this.scene.add.image(x, y, 'FractalPlugin');

        return image;
    }

    drawJulia ()
    {
        let cw = this.canvas.width;
        let ch = this.canvas.height;
        let imageData = this.context.createImageData(cw, ch);
        let data = imageData.data;

        let x0 = -0.4;
        let y0 = -0.6;

        for (let i = 0; i < ch; i++)
        {
            for (let j = 0; j < cw; j++)
            {
                // limit the axis
                let x = -1.5 + j * 3.0 / cw;
                let y = -1.0 + i * 2.0 / ch;
                
                let iteration = 0;
                
                while ((x * x + y * y < 4) && (iteration < this.maxIterations))
                {
                    let xN = x * x - y * y + x0;
                    let yN = 2 * x * y + y0;
                    x = xN;
                    y = yN;
                    iteration++;
                }
                
                // set pixel color [r,g,b,a]
                data[i * cw * 4 + j * 4 + 0] = iteration * 8;
                data[i * cw * 4 + j * 4 + 1] = iteration * 5;
                data[i * cw * 4 + j * 4 + 2] = iteration * 25;
                data[i * cw * 4 + j * 4 + 3] = 255;
            }       
        }

        this.context.putImageData(imageData, 0, 0);
    }

}

const config = {
    type: Phaser.AUTO,
    parent: 'phaser-example',
    width: 800,
    height: 600,
    plugins: {
        scene: [
            { key: 'fractalPlugin', plugin: FractalPlugin, mapping: 'fractals' }
        ]
    },
    scene: {

        create: create
    }
};

let game = new Phaser.Game(config);

function create ()
{
    this.fractals.create(400, 300, 800, 600);
}

Что такое Scene Plugin и зачем он нужен

Scene Plugin — это специальный класс, который расширяет функциональность отдельной игровой сцены (Phaser.Scene). В отличие от глобальных плагинов, он «живет» только в рамках своей сцены и имеет к ней прямой доступ.

Основные преимущества: * **Инкапсуляция логики**: Весь код для работы с фракталами находится в одном классе. * **Повторное использование**: Плагин можно легко подключить к любой другой сцене в проекте. * **Удобный API**: Плагин «маппится» (связывается) в свойство сцены (например, this.fractals), предоставляя чистый и понятный интерфейс для использования.

В нашем примере плагин будет создавать Canvas-текстуру с фракталом и возвращать готовое изображение (Phaser.GameObjects.Image), которое можно сразу добавить на сцену.

Структура класса плагина

Класс плагина наследуется от Phaser.Plugins.ScenePlugin. В конструкторе мы вызываем родительский конструктор и инициализируем внутренние свойства.

class FractalPlugin extends Phaser.Plugins.ScenePlugin {
    constructor (scene, pluginManager) {
        super(scene, pluginManager);
        this.texture;
        this.canvas;
        this.context;
        this.maxIterations = 100;
    }
}
*   `scene` и `pluginManager` передаются движком Phaser автоматически при создании плагина.
*   `this.texture` будет хранить ссылку на созданную Canvas-текстуру.
*   `this.canvas` и `this.context` — это ссылки на DOM-элемент `<canvas>` и его 2D-контекст для непосредственного рисования.
*   `this.maxIterations` — параметр алгоритма, который влияет на детализацию и цвет фрактала.

Основной метод create: от текстуры к изображению

Публичный метод create — это точка входа для использования плагина из кода сцены. Его задача — создать ресурсы, нарисовать фрактал и вернуть игровой объект.

create (x, y, w, h) {
    this.texture = this.scene.textures.createCanvas('FractalPlugin', w, h);
    this.canvas = this.texture.canvas;
    this.context = this.texture.context;
    this.drawJulia();
    this.texture.refresh();
    let image = this.scene.add.image(x, y, 'FractalPlugin');
    return image;
}
1.  `this.scene.textures.createCanvas('FractalPlugin', w, h)` создает в менеджере текстур сцены новую текстуру типа Canvas с заданным ключом и размерами.
2.  Мы сохраняем ссылки на canvas и его контекст для последующей работы.
3.  `this.drawJulia()` — вызываем наш приватный метод, который производит все вычисления и рисует на canvas.
4.  `this.texture.refresh()` — критически важный вызов. Он сообщает Phaser, что исходный canvas был изменен, и текстуру необходимо обновить на GPU.
5.  `this.scene.add.image(x, y, 'FractalPlugin')` создает игровой объект Image, использующий нашу только что созданную и заполненную текстуру.
6.  Метод возвращает этот объект, и сцена может работать с ним как с любым другим изображением.

Магия в деталях: алгоритм множества Жюлиа

Метод drawJulia реализует алгоритм построения фрактала Жюлиа. Вся работа ведется на уровне пикселей через ImageData.

drawJulia () {
    let cw = this.canvas.width;
    let ch = this.canvas.height;
    let imageData = this.context.createImageData(cw, ch);
    let data = imageData.data;
    let x0 = -0.4;
    let y0 = -0.6;
    for (let i = 0; i < ch; i++) {
        for (let j = 0; j < cw; j++) {
            // Преобразуем координаты пикселя в точку на комплексной плоскости
            let x = -1.5 + j * 3.0 / cw;
            let y = -1.0 + i * 2.0 / ch;
            let iteration = 0;
            // Итеративная формула Жюлиа
            while ((x * x + y * y < 4) && (iteration < this.maxIterations)) {
                let xN = x * x - y * y + x0;
                let yN = 2 * x * y + y0;
                x = xN;
                y = yN;
                iteration++;
            }
            // Раскрашиваем пиксель в зависимости от числа итераций
            data[i * cw * 4 + j * 4 + 0] = iteration * 8;   // R
            data[i * cw * 4 + j * 4 + 1] = iteration * 5;   // G
            data[i * cw * 4 + j * 4 + 2] = iteration * 25;  // B
            data[i * cw * 4 + j * 4 + 3] = 255;             // A
        }
    }
    this.context.putImageData(imageData, 0, 0);
}

* createImageData создает пустой буфер пикселей размером с наш canvas. * Вложенные циклы проходят по каждому пикселю изображения. * Координаты пикселя (`j,i) преобразуются в точку (x,y`) на ограниченной области комплексной плоскости. * Цикл while вычисляет, принадлежит ли эта точка множеству Жюлиа (для констант x0, y0). Количество итераций до выхода из цикла определяет цвет. * Цвет задается путем запива значений в массив data. Каждый пиксель описывается четырьмя последовательными числами (RGBA). * putImageData записывает рассчитанный массив пикселей обратно в контекст canvas.

Подключение плагина к игре

Чтобы плагин стал доступен в сцене, его необходимо зарегистрировать в конфигурации игры.

const config = {
    type: Phaser.AUTO,
    // ... другие настройки ...
    plugins: {
        scene: [
            { key: 'fractalPlugin', plugin: FractalPlugin, mapping: 'fractals' }
        ]
    },
    scene: {
        create: create
    }
};

В секции plugins.scene указывается массив плагинов для сцены. Каждый плагин описывается объектом: * key: внутренний ключ для менеджера плагинов. * plugin: ссылка на класс плагина. * mapping: имя свойства, под которым экземпляр плагина будет доступен в сцене. Именно благодаря этому мы можем вызывать this.fractals.create().

Использование в сцене становится тривиальным:

function create () {
    this.fractals.create(400, 300, 800, 600);
}

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

Создание Scene Plugin — это элегантный способ добавить в ваш проект Phaser сложную, но хорошо организованную функциональность. Мы не только научились рисовать фракталы, но и освоили работу с Canvas-текстурами и их интеграцию в игровой цикл. **Идеи для экспериментов:** 1. Анимируйте фрактал, плавно меняя константы x0 и y0 в методе drawJulia и вызывая refresh() каждый кадр. 2. Добавьте параметры в метод create для управления цветовой палитрой или типом фрактала (например, Мандельброта). 3. Используйте этот подход для генерации уникальных текстур ландшафта, неба или текстур для частиц прямо во время выполнения игры. 4. Оптимизируйте алгоритм, например, предварительно рассчитав цветовую карту (color map) для значений iteration.