О чем этот пример

Создание плавных и визуально приятных переходов между игровыми сценами — ключевой элемент для улучшения пользовательского опыта. Резкие смены экранов могут разрывать погружение в игру. Встроенная в Phaser 3 система переходов позволяет анимировать завершение одной сцены и начало другой, создавая целостное впечатление. В этой статье мы разберем конкретный пример, который демонстрирует, как использовать API `this.scene.transition()` и события жизненного цикла перехода для управления анимациями. Вы научитесь передавать данные между сценами и синхронизировать анимации с длительностью перехода.

Версия Phaser: код и демо в этой статье рассчитаны на Phaser 3.90.0.

Живой запуск

Ниже встроен рабочий билд примера. Оригинальный источник: GitHub.

Исходный код


class SceneA extends Phaser.Scene
{
    constructor ()
    {
        super({ key: 'sceneA' });

        this.face;
    }

    preload ()
    {
        this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
        this.load.image('face', 'assets/pics/bw-face.png');
    }

    create ()
    {
        this.face = this.add.image(0, 0, 'face').setOrigin(0);

        this.input.once('pointerdown', function ()
        {

            this.scene.transition({
                target: 'sceneB',
                duration: 2000,
                moveBelow: true,
                onUpdate: this.transitionOut,
                data: { x: 400, y: 300 }
            });

        }, this);
    }

    transitionOut (progress)
    {
        this.face.y = (600 * progress);
    }
}

class SceneB extends Phaser.Scene
{
    constructor ()
    {
        super({ key: 'sceneB' });
    }

    preload ()
    {
        // this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
        this.load.image('arrow', 'assets/sprites/longarrow.png');
        this.load.image('planet', 'assets/tests/space/purple-planet.png');
    }

    create (data)
    {
        const planet = this.add.image(data.x, data.y, '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', () => { console.log('Complete'); });

        this.events.on('transitionout', function (toScene, duration)
        {

            this.tweens.add({
                targets: planet,
                scaleX: 0,
                scaleY: 0,
                duration: duration
            });

        }, this);

        this.arrow = this.add.sprite(400, 300, 'arrow').setOrigin(0, 0.5);

        this.input.once('pointerdown', function (event)
        {

            this.scene.transition({
                target: 'sceneC',
                duration: 3000
            });

        }, this);
    }

    update (time, delta)
    {
        this.arrow.rotation += 0.01;
    }
}

class SceneC extends Phaser.Scene
{
    constructor ()
    {
        super({ key: 'sceneC' });
    }

    preload ()
    {
        // this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
        this.load.image('mech', 'assets/pics/titan-mech.png');
    }

    create ()
    {
        this.add.sprite(Phaser.Math.Between(0, 800), 300, 'mech');

        this.input.once('pointerdown', function (event)
        {

            this.scene.start('sceneA');

        }, this);
    }
}

const config = {
    type: Phaser.AUTO,
    width: 800,
    height: 600,
    backgroundColor: '#000000',
    parent: 'phaser-example',
    scene: [ SceneA, SceneB, SceneC ]
};

const game = new Phaser.Game(config);

Инициируем переход из SceneA

Вся механика запускается по клику мыши в первой сцене. Метод this.scene.transition() вызывается с конфигурационным объектом, который определяет параметры процесса.

this.scene.transition({
    target: 'sceneB',
    duration: 2000,
    moveBelow: true,
    onUpdate: this.transitionOut,
    data: { x: 400, y: 300 }
});

Разберем параметры: - target: Ключ сцены, на которую мы переходим ('sceneB'). - duration: Длительность перехода в миллисекундах (2000 мс). - moveBelow: Указывает, что текущая сцена (SceneA) будет перемещена под целевую сцену (SceneB) в стеке сцен. Это важно, так как сцена A продолжает рендериться и анимироваться во время перехода. - onUpdate: Функция обратного вызова, которая будет выполняться на каждом кадре во время "ухода" сцены A. В нее передается значение прогресса от 0 до 1. - data: Произвольные данные, которые будут переданы в метод create целевой сцены. В нашем случае — координаты {x: 400, y: 300}.

Функция transitionOut анимирует изображение, двигая его по оси Y в зависимости от прогресса.

transitionOut (progress) {
    this.face.y = (600 * progress);
}

Обработка перехода в целевой сцене (SceneB)

Когда переход начинается, в целевой сцене (SceneB) срабатывают специальные события, на которые можно подписаться. В примере используются три события: transitionstart, transitioncomplete и transitionout.

Событие transitionstart срабатывает в момент начала перехода на эту сцену. В обработчике мы запускаем твин для плавного появления планеты, используя переданные из SceneA координаты. Длительность твина синхронизирована с длительностью всего перехода.

this.events.on('transitionstart', function (fromScene, duration) {
    this.tweens.add({
        targets: planet,
        scaleX: 1,
        scaleY: 1,
        duration: duration
    });
}, this);

Событие transitionout срабатывает, когда из этой сцены (SceneB) начинается переход на другую (в нашем случае, по клику, на SceneC). Здесь мы запускаем обратный твин, чтобы скрыть планету.

this.events.on('transitionout', function (toScene, duration) {
    this.tweens.add({
        targets: planet,
        scaleX: 0,
        scaleY: 0,
        duration: duration
    });
}, this);

Событие transitioncomplete просто логирует завершение перехода в консоль. Оно полезно для отладки или запуска пост-обработки.

Важный нюанс: метод create сцены SceneB получает переданные ранее данные через параметр data.

create (data) {
    const planet = this.add.image(data.x, data.y, 'planet').setScale(0);
    // ...
}

Возврат к началу и простые переходы

Из SceneB по клику инициируется еще один переход, на этот раз на SceneC. Конфигурация этого перехода проще: указана только цель и длительность.

this.scene.transition({
    target: 'sceneC',
    duration: 3000
});

Поскольку не указаны onUpdate и moveBelow, сцена B будет просто скрыта через 3 секунды, а сцена C появится. В SceneC не обрабатываются события перехода, она просто создает спрайт и по клику выполняет жесткий сброс всей игры к SceneA с помощью this.scene.start().

this.scene.start('sceneA');

Важно понимать разницу: this.scene.start() немедленно останавливает текущую сцену и запускает целевую без какой-либо анимации. this.scene.transition() — это инструмент для плавной, управляемой смены.

Ключевые концепции и API

1. **Стек сцен**: Phaser управляет сценами как стеком. Параметр moveBelow в конфиге перехода контролирует положение старой сцены относительно новой в этом стеке, что влияет на порядок отрисовки. 2. **Прогресс перехода**: Функция, переданная в onUpdate, получает параметр progress (от 0 до 1), который линейно увеличивается в течение указанной duration. Это позволяет вручную анимировать любые свойства. 3. **События жизненного цикла перехода**: - transitionstart: Целевая сцена получает уведомление о начале перехода на нее. - transitioncomplete: Целевая сцена получает уведомление об окончании перехода на нее. - transitionout: Сцена получает уведомление о начале перехода с нее на другую. 4. **Передача данных**: Объект data в конфигурации transition() — это простой и эффективный способ передать состояние (координаты, счет, выбранные предметы) из одной сцены в другую.

Что попробовать дальше

Встроенный в Phaser 3 механизм переходов между сценами — мощный и гибкий инструмент. Он позволяет уйти от резких склеек экранов к кинематографичным переходам, улучшая восприятие игры. Для экспериментов попробуйте: - Использовать разные функции плавности (easing) в твинах, запускаемых во время событий перехода. - Анимировать не только позицию и масштаб, но и альфа-канал, угол поворота или даже применять шейдерные эффекты на основе progress. - Создать кастомный визуальный эффект (например, затемнение или «выцветание через белый») в функции onUpdate исходной сцены. - Реализовать очередь из нескольких сцен, переходящих друг в друга по цепочке с разными эффектами.