О чем этот пример
Динамическое редактирование текстур — мощный инструмент, который позволяет создавать визуальные эффекты прямо во время работы игры. Вместо того чтобы загружать десятки предварительно обработанных изображений, вы можете модифицировать их программно, экономя ресурсы и добавляя гибкости. В этой статье мы разберем пример, где на основе обычного спрайта создается его красный силуэт, используя Canvas API и работу с пиксельными данными.
Версия Phaser: код и демо в этой статье рассчитаны на Phaser 3.90.0.
Живой запуск
Ниже встроен рабочий билд примера. Оригинальный источник: GitHub.
Исходный код
class Example extends Phaser.Scene
{
originalTexture;
context;
preload ()
{
this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.image('dude', 'assets/sprites/phaser-dude.png');
}
create ()
{
this.originalTexture = this.textures.get('dude').getSourceImage();
const newTexture = this.textures.createCanvas('dude_new', this.originalTexture.width, this.originalTexture.height);
this.context = newTexture.getSourceImage().getContext('2d');
this.context.drawImage(this.originalTexture, 0, 0);
const dude = this.add.image(100, 100, 'dude');
const dude2 = this.add.image(200, 100, 'dude_new');
this.createSilhouette();
}
createSilhouette()
{
const pixels = this.context.getImageData(0, 0, this.originalTexture.width, this.originalTexture.height);
for (let i = 0; i < pixels.data.length / 4; i++)
{
this.processPixel(pixels.data, i * 4, 0.1);
}
this.context.putImageData(pixels, 0, 0);
}
processPixel(data, index)
{
data[index] = 255;
data[index + 1] = 0;
data[index + 2] = 0;
}
}
const config = {
type: Phaser.CANVAS,
parent: 'phaser-example',
width: 800,
height: 600,
scene: Example
};
const game = new Phaser.Game(config);
Создание текстуры-холста
Перед модификацией необходимо создать новую текстуру, которая будет выступать в роли холста для рисования. Phaser предоставляет для этого метод this.textures.createCanvas.
const newTexture = this.textures.createCanvas('dude_new', this.originalTexture.width, this.originalTexture.height);
Этот код создает новую текстуру с ключом 'dude_new' и размерами, идентичными исходному спрайту 'dude'. Важно отметить, что эта текстура сразу становится доступной в кэше текстур игры.
Для последующего рисования нам нужен контекст 2D этого холста, который получается стандартным для Canvas способом.
this.context = newTexture.getSourceImage().getContext('2d');
Получив контекст, мы копируем на этот холст исходное изображение. Теперь у нас есть две идентичные текстуры в памяти: оригинальная и её копия на холсте, готовую к манипуляциям.
Извлечение и итерация по пикселям
Сердце примера — функция createSilhouette. Вся магия происходит на уровне пикселей. Для работы с ними используется ImageData — объект, содержащий одномерный массив данных каждого пикселя.
const pixels = this.context.getImageData(0, 0, this.originalTexture.width, this.originalTexture.height);
Метод getImageData возвращает объект, свойство data которого — это Uint8ClampedArray. В этом массиве данные о цвете каждого пикселя хранятся последовательно в формате RGBA: красный (R), зеленый (G), синий (B) и альфа-канал (A, прозрачность). Таким образом, информация об одном пикселе занимает 4 элемента массива.
Цикл перебирает все пиксели изображения. Условие i < pixels.data.length / 4 гарантирует, что мы обработаем каждый пиксель ровно один раз. Для каждого пикселя вызывается функция processPixel, которой передается массив данных и индекс начала данных этого пикселя (i * 4).
Логика обработки одного пикселя
Функция processPixel — это место, где определяется конечный визуальный эффект. В нашем примере она реализует простейшую логику для создания красного силуэта.
processPixel(data, index)
{
data[index] = 255; // Красный канал (R)
data[index + 1] = 0; // Зеленый канал (G)
data[index + 2] = 0; // Синий канал (B)
}
Код игнорирует исходные цвета пикселя. Для каждого из них он устанавливает значение красного канала на максимум (255), а зеленый и синий — в ноль. Альфа-канал (data[index + 3]) не трогается, поэтому исходная прозрачность (форма спрайта) сохраняется. В результате все непрозрачные пиксели становятся чисто красными.
После обработки всего массива пикселей, измененные данные необходимо вернуть на холст.
this.context.putImageData(pixels, 0, 0);
Вызов putImageData применяет модифицированный массив pixels к контексту, и текстура 'dude_new' мгновенно обновляется на экране.
Практическое применение и демонстрация
В методе create демонстрируется итог работы: на сцену добавляются два спрайта — оригинальный и модифицированный.
const dude = this.add.image(100, 100, 'dude');
const dude2 = this.add.image(200, 100, 'dude_new');
Вызывая this.createSilhouette(), мы запускаем процесс перекрашивания второго спрайта. Такой подход открывает двери для множества эффектов: от затемнения или обесцвечивания спрайтов при получении урона до динамического создания теней, подсветки или цветовой фильтрации целых слоев игры, не загружая дополнительные ассеты.
Что попробовать дальше
Работа с ImageData и Canvas-текстурами — это низкоуровневый, но крайне эффективный способ контролировать графику в Phaser. Вы научились создавать текстуру-холст, читать и модифицировать её пиксели. Для экспериментов попробуйте изменить processPixel: реализуйте сепию, инверсию цвета или сделайте силуэт не красным, а полупрозрачным черным для эффекта тени. Можно также изменять не все пиксели, а только те, чья яркость или альфа-канал превышает определенный порог.
