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

Видео в играх может быть не просто фоном, а интерактивным элементом. Возможность динамической смены видео-источника открывает путь к созданию нелинейных кат-сцен, реактивных фонов или обучающих роликов, которые меняются в зависимости от действий игрока. В этой статье мы разберем, как загрузить несколько видео и переключаться между ними во время выполнения игры, используя метод `changeSource()` объекта `Phaser.GameObjects.Video`.

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

Живой запуск

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

Исходный код


class Example extends Phaser.Scene
{
    constructor ()
    {
        super();

        this.intro;
        this.debug;
    }

    preload ()
    {
        this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
        this.load.video('mountains', 'assets/video/mountains.mp4', true);
        this.load.video('pumpkins', 'assets/video/pumpkins.mp4', true);
    }

    create ()
    {
        //  960 x 540
        this.intro = this.add.video(0, 0, 'pumpkins').setOrigin(0);

        this.intro.on('locked', () => {

            let message = this.add.text(480, 100, 'Click to play video', { font: '32px Courier', fill: '#00ff00' }).setShadow(1, 1).setOrigin(0.5);

            this.intro.on('unlocked', () => {

                message.destroy();

            });

        });

        this.intro.play(true);

        this.debug = this.add.text(10, 10, '', { font: '22px Courier', fill: '#ffffff' }).setShadow(1, 1);

        //  Listen for the 'play' event to create our input handler
        this.intro.once('play', () => {

            this.input.on('pointerdown', () => {

                if (this.intro.getVideoKey() === 'pumpkins')
                {
                    this.intro.changeSource('mountains', true, true);
                }
                else
                {
                    this.intro.changeSource('pumpkins', true, true);
                }

            });

        });
    }

    update ()
    {
        this.debug.setText([
            'Current Time: ' + this.intro.getCurrentTime() + ' / ' + this.intro.getDuration(),
            'Click to change video'
        ]);
    }
}

const config = {
    type: Phaser.AUTO,
    width: 960,
    height: 540,
    backgroundColor: '#000000',
    parent: 'phaser-example',
    scene: Example
};

let game = new Phaser.Game(config);

Подготовка сцены и загрузка видео

Для работы с видео в Phaser необходимо создать класс сцены, который будет управлять их загрузкой и воспроизведением. Ключевой этап — предварительная загрузка всех необходимых видеофайлов в методe preload(). Это гарантирует, что контент будет доступен для мгновенного переключения.

В методе preload() используется this.load.video(). Параметр true в конце означает, что видео загрузится сразу, а не по запросу, что важно для плавной замены.

preload ()
{
    this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
    this.load.video('mountains', 'assets/video/mountains.mp4', true);
    this.load.video('pumpkins', 'assets/video/pumpkins.mp4', true);
}

После загрузки видео доступны в кеше под ключами 'mountains' и 'pumpkins'. Мы будем использовать эти ключи для их создания и смены.

Создание видеообъекта и обработка пользовательского ввода

В методе create() мы создаем экземпляр видео, размещаем его в начале координат и запускаем воспроизведение. Важный нюанс — воспроизведение видео в браузере часто требует жеста пользователя. Phaser предоставляет события 'locked' и 'unlocked' для обработки этой политики.

create ()
{
    //  960 x 540
    this.intro = this.add.video(0, 0, 'pumpkins').setOrigin(0);

    this.intro.on('locked', () => {
        let message = this.add.text(480, 100, 'Click to play video', { font: '32px Courier', fill: '#00ff00' }).setShadow(1, 1).setOrigin(0.5);
        this.intro.on('unlocked', () => {
            message.destroy();
        });
    });

    this.intro.play(true);
}

Когда видео "разблокировано" (пользователь взаимодействовал со страницей), мы убираем текстовую подсказку и создаем обработчик клика мыши. Обратите внимание, что этот обработчик добавляется только после события 'play' видео, чтобы избежать преждевременных кликов.

Динамическая смена видео с помощью changeSource

Сердце примера — метод changeSource() объекта this.intro. Он позволяет заменить текущее видео на другое, уже загруженное в кеш, прямо во время выполнения.

this.input.on('pointerdown', () => {
    if (this.intro.getVideoKey() === 'pumpkins')
    {
        this.intro.changeSource('mountains', true, true);
    }
    else
    {
        this.intro.changeSource('pumpkins', true, true);
    }
});

В этом коде при каждом клике проверяется, какое видео сейчас играет (с помощью getVideoKey()), и на него подставляется альтернативное. Метод changeSource() принимает три аргумента: ключ нового видео, должен ли оно автоматически воспроизводиться (true), и должен ли сброситься текущий прогресс воспроизведения (true). Это позволяет мгновенно переключаться между роликами.

Отладка и отображение состояния видео

Для наглядности в примере реализован простой интерфейс отладки. В методе update() каждому кадру обновляется текстовое поле, отображающее текущее время воспроизведения и общую длительность видео.

update ()
{
    this.debug.setText([
        'Current Time: ' + this.intro.getCurrentTime() + ' / ' + this.intro.getDuration(),
        'Click to change video'
    ]);
}

Здесь используются методы getCurrentTime() и getDuration() объекта this.intro. Такой подход полезен для тестирования и понимания того, как ведет себя видео при переключении. В реальном проекте эту информацию можно использовать, например, для синхронизации событий игры с конкретными моментами в видео.

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

Метод changeSource() превращает статичное видео в динамический игровой актив. Вы можете использовать этот подход для создания интерактивных инструкций, где следующий шаг объясняется в новом видео, или для фонов, реагирующих на смену уровня. Поэкспериментируйте: попробуйте переключать видео не по клику, а по таймеру или при столкновении игрока с объектом, добавьте плавное затемнение между переходами или используйте событие 'complete' одного видео для автоматического запуска следующего, создавая цепочку сцен.