О чем этот пример
В современных играх и интерактивных проектах важно не только содержание, но и его представление. Плавные переходы между различными визуальными эффектами или сценами делают опыт пользователя более цельным и профессиональным. Этот пример демонстрирует, как в Phaser 3 можно использовать видео в качестве динамической маски для переключения между двумя совершенно разными эффектами — шейдером и видео. Техника позволяет создавать сложные и впечатляющие переходы без написания сложного кода для анимации.
Версия Phaser: код и демо в этой статье рассчитаны на Phaser 3.90.0.
Живой запуск
Ниже встроен рабочий билд примера. Оригинальный источник: GitHub.
Исходный код
class Example extends Phaser.Scene
{
constructor ()
{
super();
this.effect1;
this.effect2;
this.transition;
this.transitionIn = false;
}
preload ()
{
this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.video('fireworks', 'assets/video/fireworks.mp4', true);
this.load.video('transition', 'assets/video/colorful-smoke-transition.webm', true);
this.load.glsl('Ghosts', 'assets/shaders/ghosts.frag');
}
create ()
{
// Our two effects - a shader and a video
this.effect1 = this.add.shader({
name: 'Ghosts',
fragmentKey: 'Ghosts',
initialUniforms: {
resolution: [ 1024, 600 ]
},
setupUniforms: (setUniform, drawingContext) => {
setUniform('time', this.game.loop.getDuration());
}
}, 512, 300, 1024, 600);
this.effect2 = this.add.video(512, 300, 'fireworks').play(true).setVisible(false);
// The transition video, it's a transparent WebM, so will only work in browsers that support this
this.transition = this.add.video(512, 300, 'transition');
let startTransition = () => {
this.time.addEvent({
delay: 3000,
callback: () => {
this.transitionIn = true;
this.transition.play();
}
});
};
this.transition.on('complete', startTransition);
startTransition();
}
update ()
{
// In the video the transition is fully covering the screen at 50% through,
// so at that point, we swap the two effects over:
if (this.transitionIn && this.transition.getProgress() >= 0.5)
{
if (!this.effect1.visible)
{
this.effect1.setVisible(true);
this.effect2.setVisible(false);
}
else
{
this.effect1.setVisible(false);
this.effect2.setVisible(true);
}
this.transitionIn = false;
}
}
}
const config = {
type: Phaser.AUTO,
width: 1024,
height: 600,
backgroundColor: '#000000',
parent: 'phaser-example',
scene: Example
};
let game = new Phaser.Game(config);
Подготовка ресурсов: шейдер и два видео
Ключевыми элементами примера являются три ресурса: два видеофайла и шейдер. Они загружаются в методе preload.
this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.video('fireworks', 'assets/video/fireworks.mp4', true);
this.load.video('transition', 'assets/video/colorful-smoke-transition.webm', true);
this.load.glsl('Ghosts', 'assets/shaders/ghosts.frag');
Первое видео (fireworks) — это основной эффект, который мы хотим показать. Второе видео (transition) — это специальное видео-переход с альфа-каналом (прозрачность). Такие видео работают только в браузерах, поддерживающих формат WebM с прозрачностью. Шейдер Ghosts загружается из файла .frag и представляет собой второй визуальный эффект.
Создание и настройка визуальных объектов
В методе create мы создаем и размещаем наши эффекты на экране.
this.effect1 = this.add.shader({
name: 'Ghosts',
fragmentKey: 'Ghosts',
initialUniforms: {
resolution: [ 1024, 600 ]
},
setupUniforms: (setUniform, drawingContext) => {
setUniform('time', this.game.loop.getDuration());
}
}, 512, 300, 1024, 600);
this.effect2 = this.add.video(512, 300, 'fireworks').play(true).setVisible(false);
Шейдер создается с указанием начальных uniform-переменных и функции setupUniforms, которая обновляет переменную time на каждой итерации рендера, обеспечивая динамичность эффекта. Видео с эффектом fireworks сразу начинает воспроизведение (play(true)) и скрывается (setVisible(false)). Изначально виден только шейдер.
this.transition = this.add.video(512, 300, 'transition');
Видео-переход создается, но не запускается сразу. Его задача — служить маской для переключения между эффектами.
Логика запуска перехода по времени и событию
Переход запускается не сразу, а через 3 секунды после начала воспроизведения. Для этого используется событие времени Phaser.
let startTransition = () => {
this.time.addEvent({
delay: 3000,
callback: () => {
this.transitionIn = true;
this.transition.play();
}
});
};
Функция startTransition запускается сразу в create и также назначается как обработчик события complete для видео-перехода. Это создает цикл: после завершения одного перехода через 3 секунды автоматически запускается следующий.
Момент переключения эффектов во время видео
Самая важная часть логики находится в методе update. Здесь мы отслеживаем прогресс воспроизведения видео-перехода.
if (this.transitionIn && this.transition.getProgress() >= 0.5)
Когда флаг transitionIn активен (видео-переход играет) и прогресс воспроизведения достиг 50%, происходит переключение видимого эффекта.
if (!this.effect1.visible)
{
this.effect1.setVisible(true);
this.effect2.setVisible(false);
}
else
{
this.effect1.setVisible(false);
this.effect2.setVisible(true);
}
this.transitionIn = false;
Код проверяет, какой эффект сейчас скрыт, и показывает его, скрывая другой. После переключения флаг transitionIn сбрасывается, чтобы предотвратить повторное действие до следующего запуска видео. Момент на 50% прогресса был выбран автором примера как точка, когда переходное видео полностью покрывает экран, обеспечивая чистый момент для замены заднего плана.
Что попробовать дальше
Этот пример предлагает мощный и относительно простой способ создания сложных визуальных переходов. Вместо программирования анимации мы используем готовое видео как маску. Для экспериментов можно попробовать: использовать разные видео-переходы с альфа-каналом; изменять момент переключения (например, на 30% или 70% прогресса); добавлять больше двух эффектов и переключаться между ними циклически или по случайному порядку; комбинировать эту технику с аудио, запуская звуковые эффекты в момент перехода.
