О чем этот пример
Создание бесшовного пользовательского опыта в игре часто требует плавного переключения между различными игровыми состояниями. Встроенная система переходов между сценами в Phaser позволяет управлять этим процессом, анимацией и порядком отрисовки, создавая профессиональные визуальные эффекты. Эта статья на практическом примере покажет, как использовать методы `transition()` и события жизненного цикла сцены для создания сложных переходов.
Версия Phaser: код и демо в этой статье рассчитаны на Phaser 3.55.2.
Живой запуск
Ниже встроен рабочий билд примера. Оригинальный источник: GitHub.
Исходный код
class Preloader extends Phaser.Scene
{
constructor ()
{
super('preloader');
}
preload ()
{
this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.image('raster', 'assets/demoscene/raster-bw-64.png');
this.load.image('planet', 'assets/tests/space/purple-planet.png');
this.load.atlas('flares', 'assets/particles/flares.png', 'assets/particles/flares.json');
}
create ()
{
this.scene.start('demo1');
}
}
class Demo1 extends Phaser.Scene
{
constructor ()
{
super('demo1');
}
create ()
{
const group = this.add.group();
group.createMultiple({ key: 'raster', repeat: 8 });
let ci = 0;
const colors = [ 0xef658c, 0xff9a52, 0xffdf00, 0x31ef8c, 0x21dfff, 0x31aade, 0x5275de, 0x9c55ad, 0xbd208c ];
const _this = this;
group.children.forEach(child =>
{
child.x = 100;
child.y = 300;
child.depth = 9 - ci;
child.tint = colors[ci];
ci++;
_this.tweens.add({
targets: child,
x: 700,
yoyo: true,
repeat: -1,
ease: 'Sine.easeInOut',
duration: 1500,
delay: 100 * ci
});
});
this.input.once('pointerup', function ()
{
const t1 = this.scene.transition({
target: 'demo2',
duration: 3000,
moveAbove: true
});
}, this);
}
}
class Demo2 extends Phaser.Scene
{
constructor ()
{
super('demo2');
}
create ()
{
const planet = this.add.image(400, 300, 'planet').setScale(0);
this.events.on('transitionstart', function (fromScene, duration)
{
this.tweens.add({
targets: planet,
scaleX: 1,
scaleY: 1,
duration: duration
});
}, this);
this.events.on('transitioncomplete', function ()
{
const particles = this.add.particles('flares');
const emitter = particles.createEmitter({
frame: [ 'red', 'blue', 'green', 'yellow' ],
x: 400,
y: 300,
speed: 200,
lifespan: 3000,
blendMode: 'ADD'
});
}, this);
this.events.on('transitionout', function (toScene, duration)
{
this.tweens.add({
targets: planet,
scaleX: 0,
scaleY: 0,
duration: duration
});
}, this);
this.input.once('pointerup', function (event)
{
const t2 = this.scene.transition({
target: 'demo3',
duration: 5000,
moveBehind: true
});
}, this);
}
}
class Demo3 extends Phaser.Scene
{
constructor ()
{
super('demo3');
this.pacX = 260;
this.pacY = 300;
}
create ()
{
this.pacX = 260;
this.pacY = 300;
const graphics = this.add.graphics();
const _this = this;
this.tweens.addCounter({
from: 0,
to: 30,
duration: 200,
yoyo: true,
repeat: -1,
onUpdate: function (tween)
{
const t = tween.getValue();
graphics.clear();
graphics.fillStyle(0xffffff, 1);
if (this.pacX < 700)
{
graphics.fillCircle(580, this.pacY, 30);
graphics.fillCircle(740, this.pacY, 30);
}
graphics.fillStyle(0xffff00, 1);
graphics.slice(this.pacX, this.pacY, 200, Phaser.Math.DegToRad(330 + t), Phaser.Math.DegToRad(30 - t), true);
graphics.fillPath();
graphics.fillStyle(0x000000, 1);
graphics.fillEllipse(this.pacX, this.pacY - 120, 30, 90);
},
callbackScope: _this
});
this.input.once('pointerup', function (event)
{
const t3 = this.scene.transition({
target: 'demo1',
duration: 5000,
moveBelow: true,
onUpdate: this.transitionOut
});
}, this);
}
transitionOut (progress)
{
this.pacX = 260 + (900 * progress);
}
}
const config = {
type: Phaser.WEBGL,
width: 800,
height: 600,
backgroundColor: '#000000',
parent: 'phaser-example',
scene: [ Preloader, Demo1, Demo2, Demo3 ]
};
const game = new Phaser.Game(config);
Инициализация перехода: метод `transition()`
Основной инструмент для запуска перехода — метод this.scene.transition(). Он вызывается из активной сцены и принимает объект конфигурации.
const t1 = this.scene.transition({
target: 'demo2',
duration: 3000,
moveAbove: true
});
В этом примере мы запускаем переход на сцену с ключом 'demo2'. Параметр duration задает длительность перехода в миллисекундах (здесь 3 секунды). Флаг moveAbove указывает, что целевая сцена (demo2) будет отрисована поверх текущей. Альтернативные флаги — moveBehind и moveBelow — управляют порядком слоев сцен в процессе перехода.
События жизненного цикла перехода
Целевая сцена получает уведомления о ходе перехода через события своего объекта this.events. Это позволяет синхронизировать анимации с процессом смены сцены.
this.events.on('transitionstart', function (fromScene, duration) {
// Анимация начинается вместе с переходом
this.tweens.add({
targets: planet,
scaleX: 1,
scaleY: 1,
duration: duration
});
}, this);
Событие 'transitionstart' срабатывает в начале перехода. Колбэк получает ссылку на предыдущую сцену (fromScene) и длительность перехода (duration). В примере это используется для запуска твина, который длится ровно столько же, сколько и сам переход.
this.events.on('transitioncomplete', function () {
// Переход завершен, можно создавать элементы
const particles = this.add.particles('flares');
const emitter = particles.createEmitter({ ... });
}, this);
Событие 'transitioncomplete' сигнализирует, что переход полностью завершен. В этот момент целевая сцена становится полностью активной. В коде примера именно здесь создается система частиц.
this.events.on('transitionout', function (toScene, duration) {
// Анимация для "выхода" сцены
this.tweens.add({
targets: planet,
scaleX: 0,
scaleY: 0,
duration: duration
});
}, this);
Событие 'transitionout' вызывается, когда из текущей сцены происходит переход к другой. Это позволяет создать анимацию "закрытия" или "исчезновения" текущей сцены.
Кастомная функция обновления `onUpdate`
Для более тонкого контроля над анимацией во время перехода можно использовать опцию onUpdate в конфигурации transition().
const t3 = this.scene.transition({
target: 'demo1',
duration: 5000,
moveBelow: true,
onUpdate: this.transitionOut // Кастомная функция
});
Функция transitionOut, указанная в onUpdate, будет вызываться на каждом кадре анимации перехода. Она получает один аргумент — значение прогресса от 0 до 1.
transitionOut (progress) {
this.pacX = 260 + (900 * progress);
}
В этом примере функция обновляет позицию pacX персонажа, плавно перемещая его по экрану пропорционально прогрессу перехода (progress). Это позволяет создавать сложные анимации, привязанные к временной шкале перехода.
Порядок сцен и флаги `moveAbove`, `moveBelow`, `moveBehind`
Флаги в конфигурации перехода управляют порядком отрисовки сцен в системе Phaser. Это критически важно для визуального эффекта.
- moveAbove: Целевая сцена запускается и отрисовывается **поверх** текущей сцены. Текущая сцена остается видимой под ней.
- moveBehind: Целевая сцена запускается и отрисовывается **под** текущей сценой. Текущая сцена остается видимой сверху.
- moveBelow: Целевая сцена запускается, а текущая сцена **перемещается под нее** в порядке отрисовки. Визуально это похоже на moveBehind, но внутренняя логика работы со стеком сцен отличается.
Выбор флага зависит от желаемого визуального эффекта. Например, moveAbove подходит для "наплыва" новой сцены поверх старой, а moveBehind — для эффекта "проваливания" текущей сцены под новую.
Что попробовать дальше
Система переходов Phaser — мощный инструмент для создания плавных и визуально привлекательных смен игровых состояний. Комбинируя метод transition() с его событиями и опцией onUpdate, можно реализовать практически любой анимационный эффект. Для экспериментов попробуйте: добавить разные easing-функции в твины во время событий перехода; использовать onUpdate для управления не одной, а множеством свойств объектов (прозрачность, вращение); создавать цепочки переходов между более чем двумя сценами.
