О чем этот пример
Render Texture в Phaser — это мощный инструмент для динамической генерации графики прямо во время выполнения игры. В этой статье мы разберем практический пример, где одна текстура становится интерактивным холстом для рисования, а затем превращается в ресурс для создания десятков анимированных спрайтов. Этот подход полезен для создания пользовательского контента, динамических эффектов, интерфейсов или процедурно генерируемых элементов игры без необходимости загружать дополнительные изображения.
Версия Phaser: код и демо в этой статье рассчитаны на Phaser 3.90.0.
Живой запуск
Ниже встроен рабочий билд примера. Оригинальный источник: GitHub.
Исходный код
class Example extends Phaser.Scene
{
preload ()
{
this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.image('brush', 'assets/sprites/brush3.png');
}
create ()
{
const rt = this.add.renderTexture(64, 64, 128, 128).setInteractive().setDepth(1001);
this.add.graphics().fillStyle(0x000000).lineStyle(1, 0xffffff).fillRect(0, 0, 128, 128).strokeRect(0, 0, 128, 128).setDepth(1000);
this.add.text(136, 8, '<- draw in here\n press SPACE to clear');
const hsv = Phaser.Display.Color.HSVColorWheel();
let i = 0;
this.input.keyboard.on('keydown_SPACE', () =>
{
rt.clear().render();
});
rt.on('pointerdown', function (pointer)
{
this.draw('brush', pointer.x - 8, pointer.y - 8, 1, hsv[i].color).render();
});
rt.on('pointermove', function (pointer)
{
if (pointer.isDown)
{
this.draw('brush', pointer.x - 8, pointer.y - 8, 1, hsv[i].color).render();
i = Phaser.Math.Wrap(i + 1, 0, 360);
}
});
const tt = rt.saveTexture('doodle');
const blocks = this.add.group({ key: 'doodle', repeat: 48, setScale: { x: 0.2, y: 0.1 } });
Phaser.Actions.GridAlign(blocks.getChildren(), {
width: 8,
height: 6,
cellWidth: 128,
cellHeight: 128
});
blocks.children.forEach(function (child)
{
this.tweens.add({
targets: child,
scaleX: 1,
scaleY: 1,
ease: 'Sine.easeInOut',
duration: 400,
delay: i * 50,
repeat: -1,
yoyo: true
});
i++;
if (i % 14 === 0)
{
i = 0;
}
}, this);
}
}
const config = {
type: Phaser.WEBGL,
parent: 'phaser-example',
width: 1024,
height: 768,
backgroundColor: '#2d2d88',
scene: Example
};
const game = new Phaser.Game(config);
Создание холста: Render Texture и Graphics
В основе примера лежит объект Render Texture. Это специальный игровой объект, который действует как динамический буфер для рисования — своего рода холст в памяти.
const rt = this.add.renderTexture(64, 64, 128, 128).setInteractive().setDepth(1001);
Здесь создается текстура размером 128x128 пикселей с координатами (64, 64) на сцене. Метод setInteractive() делает ее чувствительной к событиям мыши/касания, а setDepth(1001) помещает ее поверх других элементов.
Чтобы визуально обозначить границы нашего холста, поверх него (но под самой текстурой из-за глубины 1000) рисуется черный прямоугольник с белой рамкой с помощью объекта Graphics.
this.add.graphics().fillStyle(0x000000).lineStyle(1, 0xffffff).fillRect(0, 0, 128, 128).strokeRect(0, 0, 128, 128).setDepth(1000);
Интерактивное рисование и управление цветом
Логика рисования привязана к событиям указателя на Render Texture. Для плавной смены цвета используется Phaser.Display.Color.HSVColorWheel(), который возвращает массив из 360 цветов, представляющих полный цветовой круг HSV.
const hsv = Phaser.Display.Color.HSVColorWheel();
let i = 0;
При нажатии (pointerdown) и перемещении с зажатой кнопкой (pointermove) мыши на текстуре вызывается метод draw().
rt.on('pointermove', function (pointer) {
if (pointer.isDown) {
this.draw('brush', pointer.x - 8, pointer.y - 8, 1, hsv[i].color).render();
i = Phaser.Math.Wrap(i + 1, 0, 360);
}
});
Метод draw() принимает ключ изображения ('brush'), координаты (с корректировкой -8 для центрирования кисти), масштаб и цвет (взятый из цветового круга). После каждого вызова draw() необходимо вызвать .render(), чтобы изменения отобразились на текстуре. Индекс цвета `iувеличивается и заворачивается в диапазон от 0 до 360 с помощьюPhaser.Math.Wrap`, обеспечивая плавный переход через все цвета радуги.
Очистить холст можно, нажав пробел. Обработчик события keydown_SPACE вызывает метод clear() и render().
this.input.keyboard.on('keydown_SPACE', () => {
rt.clear().render();
});
Сохранение текстуры и создание группы спрайтов
Самая интересная часть — это возможность превратить нарисованную текстуру в переиспользуемый ресурс.
const tt = rt.saveTexture('doodle');
Метод saveTexture() сохраняет текущее содержимое Render Texture в текстуру кэша с ключом 'doodle'. Теперь к этой текстуре можно обращаться как к обычному загруженному изображению.
Сразу после сохранения создается группа (Group) из 48 спрайтов, каждый из которых использует нашу свежесозданную текстуру 'doodle'.
const blocks = this.add.group({ key: 'doodle', repeat: 48, setScale: { x: 0.2, y: 0.1 } });
Параметр setScale инициализирует все спрайты в группе с уменьшенным масштабом (0.2 по X, 0.1 по Y). Затем с помощью Phaser.Actions.GridAlign все спрайты выравниваются в сетку 8x6.
Phaser.Actions.GridAlign(blocks.getChildren(), {
width: 8,
height: 6,
cellWidth: 128,
cellHeight: 128
});
Анимация спрайтов на основе текстуры
Завершающий штрих — добавление пульсирующей анимации к каждому спрайту в группе с помощью твинов.
blocks.children.forEach(function (child) {
this.tweens.add({
targets: child,
scaleX: 1,
scaleY: 1,
ease: 'Sine.easeInOut',
duration: 400,
delay: i * 50,
repeat: -1,
yoyo: true
});
i++;
if (i % 14 === 0) { i = 0; }
}, this);
Для каждого дочернего спрайта создается твин, который анимирует его масштаб от начального маленького значения до 1 (полный размер). Параметры ease: 'Sine.easeInOut' и yoyo: true создают плавное пульсирующее движение. Задержка (delay: i * 50) для каждого следующего спрайта создает волнообразный эффект анимации по всей сетке. Счетчик `i` сбрасывается каждые 14 спрайтов, чтобы паттерн задержек повторялся.
Что попробовать дальше
Render Texture открывает путь к динамическому созданию контента в Phaser. Вы можете использовать этот подход не только для рисования, но и для компоновки сложных спрайтов из частей, создания динамических мини-карт, масок или уникальных эффектов повреждений. Попробуйте экспериментировать: сохраняйте несколько разных текстур в течение сессии, комбинируйте рисование с физическими телами или используйте содержимое текстуры как источник для шейдеров.
