О чем этот пример
Работа с цветом — мощный инструмент для создания визуальных эффектов в играх. Прямое манипулирование пикселями текстуры позволяет динамически менять её внешний вид без загрузки дополнительных ассетов. В этой статье мы разберем, как в Phaser 3 можно программно сдвигать цветовой тон (hue) текстуры, используя Canvas API и встроенные функции конвертации цветовых моделей. Этот метод полезен для создания эффектов заклинаний, смены времени суток, индикации состояния персонажа или генерации вариаций врагов из одной базовой текстуры.
Версия Phaser: код и демо в этой статье рассчитаны на Phaser 3.90.0.
Живой запуск
Ниже встроен рабочий билд примера. Оригинальный источник: GitHub.
Исходный код
class Example extends Phaser.Scene
{
originalTexture;
newTexture;
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();
this.newTexture = this.textures.createCanvas('dudeNew', this.originalTexture.width, this.originalTexture.height);
this.context = this.newTexture.getSourceImage().getContext('2d');
this.context.drawImage(this.originalTexture, 0, 0);
this.add.image(100, 100, 'dude');
this.add.image(200, 100, 'dudeNew');
this.time.addEvent({ delay: 500, callback: () => this.hueShift(), loop: true });
}
hueShift ()
{
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);
this.newTexture.refresh();
}
processPixel (data, index, deltahue)
{
const r = data[index];
const g = data[index + 1];
const b = data[index + 2];
const hsv = Phaser.Display.Color.RGBToHSV(r, g, b);
const h = hsv.h + deltahue;
const rgb = Phaser.Display.Color.HSVToRGB(h, hsv.s, hsv.v);
data[index] = rgb.r;
data[index + 1] = rgb.g;
data[index + 2] = rgb.b;
}
}
const config = {
type: Phaser.AUTO,
parent: 'phaser-example',
width: 800,
height: 600,
scene: Example
};
const game = new Phaser.Game(config);
Подготовка сцены и загрузка текстур
Вся работа происходит в классе сцены. На этапе preload загружается исходное изображение. В методе create мы получаем доступ к загруженной текстуре и создаем новую, пустую текстуру на основе Canvas, которая будет служить целевой для наших манипуляций.
preload ()
{
this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.image('dude', 'assets/sprites/phaser-dude.png');
}
create ()
{
// Получаем исходное HTMLImageElement из текстуры 'dude'
this.originalTexture = this.textures.get('dude').getSourceImage();
// Создаем новую текстуру типа Canvas с тем же размером
this.newTexture = this.textures.createCanvas('dudeNew', this.originalTexture.width, this.originalTexture.height);
// Получаем 2D-контекст canvas новой текстуры для рисования
this.context = this.newTexture.getSourceImage().getContext('2d');
}
Копирование и периодическое обновление
После получения контекста мы рисуем на нём исходное изображение. Затем добавляем на сцену два спрайта: один с оригинальной текстурой, другой — с нашей новой, пока ещё неизменённой. Чтобы увидеть анимацию, мы настраиваем таймер, который будет регулярно вызывать функцию сдвига оттенка.
create ()
{
// ... предыдущий код
// Копируем пиксели оригинальной текстуры на canvas новой текстуры
this.context.drawImage(this.originalTexture, 0, 0);
// Добавляем оба спрайта на сцену для сравнения
this.add.image(100, 100, 'dude');
this.add.image(200, 100, 'dudeNew');
// Запускаем таймер, который каждые 500 мс вызывает hueShift()
this.time.addEvent({ delay: 500, callback: () => this.hueShift(), loop: true });
}
Манипуляция пикселями: сдвиг в HSV
Ключевой метод hueShift работает с данными пикселей. Мы получаем массив ImageData из контекста, проходим по каждому пикселю и изменяем его цвет, после чего записываем измененные данные обратно и обновляем текстуру.
hueShift ()
{
// Получаем объект ImageData, содержащий массив data (RGBA)
const pixels = this.context.getImageData(0, 0, this.originalTexture.width, this.originalTexture.height);
// Проходим по каждому пикселю (4 элемента массива на пиксель: R, G, B, A)
for (let i = 0; i < pixels.data.length / 4; i++)
{
this.processPixel(pixels.data, i * 4, 0.1);
}
// Возвращаем измененные пиксели в контекст
this.context.putImageData(pixels, 0, 0);
// Сообщаем текстуре Phaser, что её источник (canvas) изменился
this.newTexture.refresh();
}
Преобразование цвета: от RGB к HSV и обратно
Сам сдвиг цвета проще выполнять в модели HSV (Hue, Saturation, Value), где тон (Hue) — это угол на цветовом круге. Phaser предоставляет встроенные утилиты для конвертации. Функция processPixel извлекает компоненты RGB, преобразует их в HSV, изменяет компоненту Hue, а затем конвертирует обратно в RGB.
processPixel (data, index, deltahue)
{
// Извлекаем компоненты цвета из массива данных
const r = data[index];
const g = data[index + 1];
const b = data[index + 2];
// Конвертируем RGB в HSV. Возвращается объект {h, s, v}
const hsv = Phaser.Display.Color.RGBToHSV(r, g, b);
// Изменяем тон. Значение h нормализовано (0-1).
const h = hsv.h + deltahue;
// Конвертируем модифицированный HSV обратно в RGB
const rgb = Phaser.Display.Color.HSVToRGB(h, hsv.s, hsv.v);
// Записываем новые значения компонент обратно в массив данных
data[index] = rgb.r;
data[index + 1] = rgb.g;
data[index + 2] = rgb.b;
// Компонента Alpha (data[index + 3]) остаётся без изменений
}
Что попробовать дальше
Вы научились динамически изменять цветовой тон текстуры в Phaser 3, используя низкоуровневый Canvas API и встроенные цветовые утилиты. Этот подход открывает дорогу для множества экспериментов. Попробуйте менять не только тон, но и насыщенность (hsv.s) или значение (hsv.v) для создания эффектов затемнения или выцветания. Можно привязать сдвиг не к таймеру, а к игровым событиям (получение урона, усиление). Также стоит изучить работу с Phaser.Display.Color для других преобразований, например, в градации серого или для применения предустановленных цветовых фильтров.
