О чем этот пример
Иногда статичных спрайтов и готовых анимаций недостаточно. Вам нужна текстура, которая меняется в реальном времени в зависимости от игровой логики, математических функций или действий игрока. Phaser предоставляет мощный инструмент — Canvas Textures, позволяющий программно генерировать и изменять изображения прямо во время выполнения игры. Эта статья покажет, как создать динамический фоновый эффект, похожий на абстрактную волну цвета, используя всего несколько строк кода. Вы научитесь работать с `this.textures.createCanvas`, управлять пикселями через контекст рисования и обновлять текстуру каждый кадр, что открывает двери для создания процедурных анимаций, симуляций частиц или уникальных визуальных эффектов без подготовки графики заранее.
Версия Phaser: код и демо в этой статье рассчитаны на Phaser 3.90.0.
Живой запуск
Ниже встроен рабочий билд примера. Оригинальный источник: GitHub.
Исходный код
const T = Math.tan;
const C = Math.cos;
const S = Math.sin;
class Example extends Phaser.Scene
{
frame = 0;
time = 0;
x;
c;
create ()
{
const canvasTexture = this.textures.createCanvas('dwitter', 1920, 1080);
this.c = canvasTexture.getSourceImage();
this.x = this.c.getContext('2d');
this.add.image(0, 0, 'dwitter').setOrigin(0).setScale(0.5);
}
update ()
{
this.time = this.frame / 60;
if (this.time * 60 | this.frame - 1 === 0)
{
this.time += 0.000001;
}
this.frame++;
this.u(this.time);
}
R (r, g, b, a)
{
a = a === undefined ? 1 : a;
return `rgba(${r|0},${g|0},${b|0},${a})`;
}
u (t)
{
let c = this.c;
let x = this.x;
let s;
let i;
s = 10; x.drawImage(c,s,0); for (i = 0; i <= c.height; i += s) { x.fillRect(0,i,s,s,x.fillStyle = `hsl(${i / c.height * C(t * i * 3) * 255},100%,50%)`); }
}
}
const config = {
width: 960,
height: 540,
type: Phaser.CANVAS,
parent: 'phaser-example',
backgroundColor: '#ffffff',
scene: Example
};
const game = new Phaser.Game(config);
Подготовка Canvas Texture
Всё начинается с создания текстуры на основе элемента HTML Canvas. В методе create() сцены мы используем встроенный API Phaser для генерации такой текстуры.
const canvasTexture = this.textures.createCanvas('dwitter', 1920, 1080);
Здесь createCanvas принимает ключ текстуры ('dwitter') и её размеры. Phaser создаёт скрытый canvas-элемент и регистрирует его как текстуру, которую можно использовать как любой другой спрайт.
this.c = canvasTexture.getSourceImage();
this.x = this.c.getContext('2d');
Метод getSourceImage() возвращает ссылку на сам HTMLCanvasElement, а getContext('2d') даёт нам инструмент для рисования на нём — классический 2D контекст.
this.add.image(0, 0, 'dwitter').setOrigin(0).setScale(0.5);
Наконец, мы добавляем изображение, которое использует нашу созданную текстуру. Установка .setOrigin(0) привязывает точку отображения к левому верхнему углу, а .setScale(0.5) уменьшает текстуру, чтобы она поместилась в окно игры размером 960x540.
Цикл обновления и управление временем
Для анимации необходимо обновлять текстуру каждый кадр. За это отвечает метод update(), который Phaser вызывает автоматически.
this.time = this.frame / 60;
Здесь мы вычисляем текущее время в секундах, предполагая 60 кадров в секунду. Переменная this.frame увеличивается каждый вызов.
if (this.time * 60 | this.frame - 1 === 0)
{
this.time += 0.000001;
}
Эта проверка — технический трюк для предотвращения возможных ошибок при очень малых значениях времени. Оператор `|` (побитовое ИЛИ) приводит результат к целому числу, обеспечивая стабильность вычислений.
this.frame++;
this.u(this.time);
После инкремента счётчика кадров мы вызываем основной метод отрисовки `u`, передавая ему текущее время.
Функция генерации цвета
Перед тем как рисовать, нам нужен удобный способ задавать цвет. Метод `R` генерирует строку в формате RGBA для контекста canvas.
R (r, g, b, a)
{
a = a === undefined ? 1 : a;
return `rgba(${r|0},${g|0},${b|0},${a})`;
}
Параметр `a(альфа, прозрачность) по умолчанию равен 1 (непрозрачный). Выражениеr|0` использует побитовое ИЛИ с нулём для быстрого приведения числа к целому, что полезно при работе с результатами тригонометрических функций.
Сердце эффекта: процедурная отрисовка
Вся магия происходит в методе u(t). Он отвечает за рисование анимированного градиента или волны.
s = 10; x.drawImage(c,s,0); for (i = 0; i <= c.height; i += s) { x.fillRect(0,i,s,s,x.fillStyle = `hsl(${i / c.height * C(t * i * 3) * 255},100%,50%)`); }
Давайте разберём эту компактную строку по шагам:
1. s = 10 — устанавливает размер квадрата (10x10 пикселей).
2. x.drawImage(c,s,0) — сдвигает всё текущее изображение на canvas на 10 пикселей вправо. Это создаёт эффект плавного горизонтального скроллинга влево.
3. Цикл for проходит по высоте canvas с шагом в 10 пикселей.
4. x.fillRect(0,i,s,s,...) — рисует квадрат в левом столбце на высоте `i`.
5. x.fillStyle =hsl(...)`` — определяет цвет квадрата.
Цвет вычисляется в модели HSL:
- **Hue (Оттенок)**: i / c.height * C(t * i * 3) * 255. Значение зависит от вертикальной позиции (`i), времени (t) и косинуса (C`). Косинус создаёт волнообразное изменение оттенка по вертикали, которое анимируется со временем.
- **Saturation (Насыщенность)**: 100% — максимальная.
- **Lightness (Яркость)**: 50% — средняя.
Именно комбинация C(t * i * 3) делает оттенок зависимым и от времени, и от позиции, создавая живую, «плывущую» цветовую волну.
Настройка игры и конфигурация
Пример завершается стандартной для Phaser конфигурацией игры.
const config = {
width: 960,
height: 540,
type: Phaser.CANVAS,
parent: 'phaser-example',
backgroundColor: '#ffffff',
scene: Example
};
Здесь важно указать type: Phaser.CANVAS, так как мы активно работаем с 2D контекстом. Фон установлен белым ('#ffffff'), чтобы контрастировать с нашей динамической текстурой.
const game = new Phaser.Game(config);
Последняя строка создаёт экземпляр игры, запуская весь описанный цикл.
Что попробовать дальше
Вы изучили, как превратить HTML Canvas в живую, обновляемую текстуру Phaser. Ключевые инструменты — this.textures.createCanvas для создания и getContext('2d') для рисования.
**Идеи для экспериментов:**
1. Замените C(t * i * 3) на S(t * i * 0.5) (синус) или T(t + i) (тангенс) для других волновых паттернов.
2. Измените fillRect на fillCircle, используя arc, для создания вертикальных рядов кругов.
3. Вместо вертикального градиента попробуйте горизонтальный: измените цикл для прохода по ширине и рисуйте столбцы.
4. Добавьте влияние мыши: используйте this.input.activePointer.x для вычисления оттенка, создавая интерактивный визуализатор.
