О чем этот пример
Расширение функциональности игрового движка под свои нужды — мощный навык для разработчика. В этой статье мы разберем, как создать собственный Scene Plugin для Phaser 3, который будет генерировать и анимировать фрактальное множество Жюлиа прямо на 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 = 64;
this.imageData;
this.data;
this.v1 = -1.5;
this.v2 = 3.0;
this.v3 = -1.0;
this.v4 = 2.0;
this.x0 = -0.4;
this.y0 = -0.6;
this.c1 = 8;
this.c2 = 5;
this.c3 = 25;
}
boot ()
{
var eventEmitter = this.systems.events;
eventEmitter.on('update', this.update, this);
}
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.imageData = this.context.createImageData(w, h);
this.data = this.imageData.data;
this.drawJulia();
this.texture.refresh();
let image = this.scene.add.image(x, y, 'FractalPlugin');
return image;
}
update ()
{
if (!this.texture)
{
return;
}
this.drawJulia();
this.texture.refresh();
}
drawJulia ()
{
let cw = this.canvas.width;
let ch = this.canvas.height;
let imageData = this.imageData;
let data = this.data;
let x0 = this.x0;
let y0 = this.y0;
for (let i = 0; i < ch; i++)
{
for (let j = 0; j < cw; j++)
{
// limit the axis
let x = this.v1 + j * this.v2 / cw;
let y = this.v3 + i * this.v4 / 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 * this.c1;
data[i * cw * 4 + j * 4 + 1] = iteration * this.c2;
data[i * cw * 4 + j * 4 + 2] = iteration * this.c3;
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 ()
{
var image = this.fractals.create(400, 300, 256, 256);
image.setScale(2);
this.tweens.add({
targets: this.fractals,
v1: 1.5,
v2: -3.0,
v3: 1.0,
v4: -2.0,
duration: 4000,
yoyo: true,
repeat: -1
});
this.tweens.add({
targets: this.fractals,
c1: 16,
c2: 10,
c3: 0,
duration: 16000,
yoyo: true,
repeat: -1
});
}
Что такое Scene Plugin и зачем он нужен
Scene Plugin — это особый тип плагина в Phaser, который привязывается к экземпляру сцены (Phaser.Scene). Он получает доступ к системам сцены (например, this.systems.events) и может реагировать на ее события, такие как update. Это идеально для функциональности, тесно связанной с жизненным циклом и отображением на конкретной сцене, например, для управления спецэффектами или, как в нашем случае, генерации динамической текстуры.
Основное преимущество — инкапсуляция. Вся логика работы с фракталом, включая математические расчеты и манипуляции с пикселями, скрыта внутри класса плагина. В основной код сцены мы просто обращаемся к его простому API.
Структура плагина: от наследования до инициализации
Класс плагина наследуется от базового Phaser.Plugins.ScenePlugin. Это дает доступ к ссылке на сцену (this.scene) и менеджеру плагинов.
В конструкторе мы инициализируем все необходимые переменные: параметры для расчета фрактала, ссылки на будущие Canvas и контекст.
Ключевой метод boot вызывается системой Phaser при старте плагина. Здесь мы подписываемся на событие update сцены, чтобы наш фрактал мог обновляться каждый кадр.
eventEmitter.on('update', this.update, this);
Создание Canvas-текстуры и публичный метод create
Публичный метод create — это точка входа для сцены. Здесь происходит магия интеграции с рендерером Phaser.
Сначала мы создаем динамическую текстуру на основе Canvas с помощью this.scene.textures.createCanvas. Эта текстура сразу попадает в кеш текстур движка.
Затем мы сохраняем ссылки на DOM-элемент canvas и его 2D-контекст (context). Для пиксельной работы создаем объект ImageData и получаем доступ к его буферу данных (data).
После отрисовки первого кадра фрактала вызываем this.texture.refresh(), чтобы Phaser знал, что текстура обновилась. Метод возвращает игровое изображение (Phaser.GameObjects.Image), созданное из этой текстуры, которое можно сразу добавить на сцену и масштабировать.
this.texture = this.scene.textures.createCanvas('FractalPlugin', w, h);
// ...
let image = this.scene.add.image(x, y, 'FractalPlugin');
return image;
Ядро плагина: алгоритм отрисовки множества Жюлиа
Метод drawJulia реализует алгоритм escape-time для фрактала Жюлиа. Он проходит по каждому пикселю Canvas, преобразует его координаты в точку на комплексной плоскости и проверяет, уходит ли итерационная последовательность в бесконечность.
Переменные v1, v2, v3, v4 определяют область комплексной плоскости, которую мы отображаем на Canvas. Константы x0 и y0 — это параметры, задающие конкретную форму фрактала.
Цвет пикселя определяется количеством итераций (iteration), умноженным на коэффициенты c1, c2, c3 (для каналов R, G, B).
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 * this.c1;
После заполнения буфера данных он переносится на Canvas с помощью this.context.putImageData.
Интеграция в игру: регистрация плагина и анимация
Чтобы Phaser знал о нашем плагине, его нужно зарегистрировать в конфигурации игры. Мы указываем его в секции plugins.scene, задаем ключ для доступа (key) и mapping — имя, под которым API плагина появится в сцене (в нашем случае this.fractals).
plugins: {
scene: [
{ key: 'fractalPlugin', plugin: FractalPlugin, mapping: 'fractals' }
]
}
В коде сцены мы создаем фрактальное изображение и сразу анимируем параметры плагина с помощью системы твинов (this.tweens). Анимация v1-v4 заставляет область просмотра "двигаться" по комплексной плоскости, а анимация c1-c3 плавно меняет цветовую палитру. Так как метод update плагина вызывается каждый кадр, эти изменения сразу визуализируются.
this.tweens.add({
targets: this.fractals,
v1: 1.5,
// ...
});
Что попробовать дальше
Создание кастомного Scene Plugin в Phaser — это прямой путь к чистой архитектуре и безграничной кастомизации. Вы можете адаптировать этот пример для генерации шумов, динамических карт высот или процедурных текстур для частиц. Для экспериментов попробуйте: заменить формулу Жюлиа на Мандельброта, привязать параметры фрактала x0/y0 к положению курсора мыши, использовать WebGL-шейдеры для расчетов вместо CPU или создать плагин для управления сложной системой погодных эффектов на сцене.
