О чем этот пример
Создание плавных, визуально привлекательных эффектов — ключ к удержанию внимания игрока. В этой статье мы разберем официальный пример из Phaser, который демонстрирует, как можно декомпозировать спрайт на отдельные пиксели и анимировать каждый из них, создавая эффект сборки-разборки изображения. Этот подход полезен для создания эпичных заставок, transition-эффектов между уровнями или визуализации магических спецэффектов, добавляя игре уникальный стиль и динамику.
Версия Phaser: код и демо в этой статье рассчитаны на Phaser 3.90.0.
Живой запуск
Ниже встроен рабочий билд примера. Оригинальный источник: GitHub.
Исходный код
class DemoB extends Phaser.Scene
{
constructor ()
{
super({ key: 'DemoB', active: true });
}
preload ()
{
this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.image('sonic', 'assets/sprites/sonic.png');
this.load.image('pixel', 'assets/sprites/16x16.png');
}
create ()
{
const source = this.textures.get('sonic').source[0].image;
const canvas = this.textures.createCanvas('pad', 38, 42).source[0].image;
const ctx = canvas.getContext('2d');
ctx.drawImage(source, 0, 0);
const imageData = ctx.getImageData(0, 0, 38, 42);
let x = 0;
let y = 0;
const color = new Phaser.Display.Color();
for (var i = 0; i < imageData.data.length; i += 4)
{
var r = imageData.data[i];
var g = imageData.data[i + 1];
var b = imageData.data[i + 2];
var a = imageData.data[i + 3];
if (a > 0)
{
var dx = 100 + x * 16;
var dy = 0 + y * 16;
var image = this.add.image(Phaser.Math.Between(0, 800), Phaser.Math.Between(0, 600), 'pixel').setScale(0);
color.setTo(r, g, b, a);
image.setTint(color.color);
this.tweens.add({
targets: image,
duration: 2000,
x: dx,
y: dy,
scaleX: 1,
scaleY: 1,
angle: 360,
delay: i / 1500,
yoyo: true,
repeat: -1,
repeatDelay: 6
});
}
x++;
if (x === 38)
{
x = 0;
y++;
}
}
const cam = this.cameras.main;
cam.setBackgroundColor('#2d2d2d');
cam.x = 800;
cam.y = 0;
}
}
Подготовка Canvas и чтение данных пикселей
В основе эффекта лежит работа с Canvas API через возможности Phaser. Цель — получить массив данных о цвете каждого пикселя исходного изображения.
В методе create() мы сначала получаем доступ к исходному изображению 'sonic' и создаем на его основе новый холст (canvas) такого же размера.
const source = this.textures.get('sonic').source[0].image;
const canvas = this.textures.createCanvas('pad', 38, 42).source[0].image;
const ctx = canvas.getContext('2d');
Затем на этот холст рисуется исходная картинка. Это необходимо, чтобы впоследствии считать данные пикселей. Метод getImageData возвращает объект ImageData, содержащий одномерный массив data.
ctx.drawImage(source, 0, 0);
const imageData = ctx.getImageData(0, 0, 38, 42);
Массив imageData.data хранит информацию о каналах RGBA (Red, Green, Blue, Alpha) для каждого пикселя подряд. То есть для одного пикселя отводится 4 элемента массива.
Итерация по пикселям и создание спрайтов
Далее в цикле мы проходим по массиву данных, обрабатывая пиксели блоками по 4 элемента.
for (var i = 0; i < imageData.data.length; i += 4)
{
var r = imageData.data[i];
var g = imageData.data[i + 1];
var b = imageData.data[i + 2];
var a = imageData.data[i + 3];
// ...
}
Ключевое условие — if (a > 0). Оно проверяет альфа-канал (прозрачность) пикселя. Таким образом, мы создаем спрайты только для непрозрачных пикселей, экономя ресурсы и не загромождая сцену невидимыми объектами.
Для каждого непрозрачного пикселя создается спрайт image из текстуры 'pixel' в случайной позиции на экране и с нулевым масштабом. Его конечная позиция (dx, dy) рассчитывается так, чтобы в итоге он встал на свое место в исходной картинке, образуя мозаику.
var image = this.add.image(Phaser.Math.Between(0, 800), Phaser.Math.Between(0, 600), 'pixel').setScale(0);
Цвет пикселя применяется к спрайту через твин. Класс Phaser.Display.Color используется для удобной работы с цветом, а метод setTint окрашивает спрайт.
color.setTo(r, g, b, a);
image.setTint(color.color);
Анимация с помощью Tween Manager
Сердце эффекта — плавная анимация, создаваемая системой твинов Phaser. Для каждого спрайта-пикселя создается отдельный твин с набором свойств.
this.tweens.add({
targets: image,
duration: 2000,
x: dx,
y: dy,
scaleX: 1,
scaleY: 1,
angle: 360,
delay: i / 1500,
yoyo: true,
repeat: -1,
repeatDelay: 6
});
Разберем ключевые параметры:
* targets: объект, который будет анимироваться.
* x, y: конечные координаты, где пиксель должен занять свое место в сетке.
* scaleX, scaleY: анимация от 0 до 1 создает эффект «вырастания» пикселя.
* angle: 360: пиксель совершит полный оборот за время анимации.
* delay: i / 1500: задержка старта твина, рассчитанная на основе индекса в массиве. Это создает волнообразный, последовательный запуск анимации для всех пикселей, а не единовременный.
* yoyo: true: после завершения анимация проиграется в обратном порядке (пиксель «свернется» и вернется в случайную точку).
* repeat: -1: бесконечное повторение всей последовательности (вперед-назад).
* repeatDelay: пауза между циклами повторения.
Настройка камеры и итоговая сцена
После создания всех анимированных пикселей код настраивает основную камеру сцены.
const cam = this.cameras.main;
cam.setBackgroundColor('#2d2d2d');
cam.x = 800;
cam.y = 0;
Эти строки выполняют две задачи:
1. setBackgroundColor устанавливает темно-серый фон для всей зоны просмотра камеры, что улучшает визуальный контраст и выделяет цветные пиксели.
2. Смещение камеры по `xиy(cam.x = 800`) фактически сдвигает весь viewport. В данном конкретном примере это, скорее всего, сделано для демонстрации в составе более крупного проекта с несколькими камерами, чтобы расположить эту сцену в определенной области экрана. В изолированной сцене это сместит видимую область, и часть пикселей может оказаться за ее границами.
Что попробовать дальше
Разобранный пример — отличная основа для создания собственных эффектов. Для экспериментов попробуйте изменить текстуру пикселя на круг или звезду, задать конечные позиции не сеткой, а по окружности или случайным образом. Можно управлять не только позицией и масштабом, но и alpha-каналом, создавая эффект постепенного появления. Заменив yoyo и repeat на однократное выполнение, вы получите красивый переход для загрузки уровня, где картинка собирается из разлетающихся частиц.
