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

Работа с видео в играх — мощный инструмент для создания атмосферных кат-сцен, фонов и спецэффектов. Однако у видео есть состояние: оно может проигрываться, быть на паузе или завершиться. В примере bugs/4910 показана распространённая проблема: как корректно отслеживать состояние видео (в частности, паузу) для синхронизации игровой логики. Эта статья разберёт код примера, покажет, как загружать и управлять несколькими видео одновременно, и объяснит, почему мониторинг свойств вроде `video.paused` критически важен для сложных сцен.

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

Живой запуск

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

Исходный код


var config = {
    type: Phaser.AUTO,
    width: 800,
    height: 600,
    backgroundColor: '#2d2d2d',
    parent: 'phaser-example',
    scene: {
        preload: preload,
        create: create,
        update: update
    }
};

var vid1, vid2, vid3, vid4;
var debug1, debug2, debug3, debug4;

var game = new Phaser.Game(config);

function preload ()
{
        this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
    // this.load.video('wormhole', 'assets/video/wormhole.mp4');
    // this.load.video('skull', 'assets/video/skull.mp4');
    // this.load.video('underwater', 'assets/video/underwater.mp4');
    // this.load.video('pumpkins', 'assets/video/pumpkins.mp4');
    // this.load.video('mountains', 'assets/video/mountains.mp4');
    // this.load.video('fireworks', 'assets/video/fireworks.mp4');

    this.load.setCORS('anonymous');
    this.load.setBaseURL('https://labs.phaser.io');

    this.load.video('wormhole', 'assets/video/wormhole.mp4', 'canplaythrough', false);
    this.load.video('skull', 'assets/video/skull.mp4', 'canplaythrough', false);
    this.load.video('underwater', 'assets/video/underwater.mp4', 'canplaythrough', false);
    this.load.video('pumpkins', 'assets/video/pumpkins.mp4', 'canplaythrough', false);
    this.load.video('mountains', 'assets/video/mountains.mp4', 'canplaythrough', false);
    this.load.video('fireworks', 'assets/video/fireworks.mp4', 'canplaythrough', false);
    this.load.video('fireworks2', 'assets/video/fireworks.mp4', 'canplaythrough', false);
    this.load.video('fireworks3', 'assets/video/fireworks.mp4', 'canplaythrough', false);
    this.load.video('fireworks4', 'assets/video/fireworks.mp4', 'canplaythrough', false);

}

function create ()
{
    vid1 = this.add.video(0, 0, 'wormhole').setScale(0.5).setOrigin(0).setAlpha(0.1);
    vid2 = this.add.video(400, 0, 'skull').setScale(0.5).setOrigin(0).setAlpha(0.1);
    vid3 = this.add.video(0, 300, 'mountains').setScale(0.5).setOrigin(0).setAlpha(0.1);
    vid4 = this.add.video(400, 300, 'pumpkins').setScale(0.5).setOrigin(0).setAlpha(0.1);

    vid1.play(true);
    vid2.play(true);
    vid3.play(true);
    vid4.play(true);

    window.v1 = vid1;
    window.v2 = vid2;
    window.v3 = vid3;
    window.v4 = vid4;

    debug1 = this.add.text(10, 10);
    debug2 = this.add.text(10, 100);
    debug3 = this.add.text(10, 200);
    debug4 = this.add.text(10, 300);
}

function update ()
{
    debug1.setText([
        vid1.video.paused
    ]);

    debug2.setText([
        vid2.video.paused
    ]);

    debug3.setText([
        vid3.video.paused
    ]);

    debug4.setText([
        vid4.video.paused
    ]);


}


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

Основная конфигурация игры стандартна: задаём размеры, цвет фона и привязываем три ключевые функции жизненного цикла сцены: preload, create и update.

var config = {
    type: Phaser.AUTO,
    width: 800,
    height: 600,
    backgroundColor: '#2d2d2d',
    parent: 'phaser-example',
    scene: {
        preload: preload,
        create: create,
        update: update
    }
};

var game = new Phaser.Game(config);

В функции preload происходит загрузка видеофайлов. Обратите внимание на несколько важных деталей. Сначала базовый URL меняется на labs.phaser.io, и устанавливается CORS для корректной загрузки с другого домена. Метод this.load.video принимает несколько параметров: ключ ассета, путь к файлу, событие готовности ('canplaythrough') и флаг false, который указывает, что видео не должно создаваться как элемент VideoTexture (это полезно для экономии памяти при множественном использовании одного файла). В примере загружаются несколько уникальных видео и одно (fireworks) используется несколько раз под разными ключами.

function preload ()
{
    this.load.setCORS('anonymous');
    this.load.setBaseURL('https://labs.phaser.io');

    this.load.video('wormhole', 'assets/video/wormhole.mp4', 'canplaythrough', false);
    this.load.video('skull', 'assets/video/skull.mp4', 'canplaythrough', false);
    // ... другие видео
    this.load.video('fireworks4', 'assets/video/fireworks.mp4', 'canplaythrough', false);
}

Создание и запуск видеообъектов

В функции create создаются четыре видеообъекта с помощью this.add.video. Каждому задаётся позиция, масштаб 0.5, точка начала трансформации в левый верхний угол (setOrigin(0)) и низкая прозрачность (setAlpha(0.1)), чтобы можно было видеть несколько слоёв одновременно. Сразу после создания видео запускаются в бесконечный цикл с помощью play(true).

function create ()
{
    vid1 = this.add.video(0, 0, 'wormhole').setScale(0.5).setOrigin(0).setAlpha(0.1);
    vid2 = this.add.video(400, 0, 'skull').setScale(0.5).setOrigin(0).setAlpha(0.1);
    vid3 = this.add.video(0, 300, 'mountains').setScale(0.5).setOrigin(0).setAlpha(0.1);
    vid4 = this.add.video(400, 300, 'pumpkins').setScale(0.5).setOrigin(0).setAlpha(0.1);

    vid1.play(true);
    vid2.play(true);
    vid3.play(true);
    vid4.play(true);
}

Также создаются четыре текстовых объекта debug1-debug4 для отображения отладочной информации. Они позиционируются по вертикали с шагом в 100 пикселей. Важный момент: видеообъекты присваиваются глобальным переменным window.v1-v4. Это сделано для удобства отладки в консоли браузера, чтобы можно было вручную проверять свойства видео.

Мониторинг состояния видео в реальном времени

Сердце примера — функция update, которая выполняется каждый кадр. Её задача — отслеживать и отображать состояние каждого видео. Ключевое свойство, которое проверяется, — vid1.video.paused. Оно возвращает true, если видео находится на паузе, и false, если проигрывается.

function update ()
{
    debug1.setText([
        vid1.video.paused
    ]);

    debug2.setText([
        vid2.video.paused
    ]);

    debug3.setText([
        vid3.video.paused
    ]);

    debug4.setText([
        vid4.video.paused
    ]);
}

Здесь vid1.video — это ссылка на нативный HTMLVideoElement, которым управляет Phaser. Мониторинг этого свойства в реальном времени позволяет реагировать на события, например, когда видео завершает воспроизведение (в данном примере цикл бесконечный, но в реальном сценарии после завершения видео может быть поставлено на паузу). Это особенно полезно для синхронизации кат-сцен с игровыми событиями или для переключения между несколькими видео.

Практическое применение: от отладки до игровой логики

Изначально этот пример был создан для отладки проблемы (bug #4910), связанной с корректным определением состояния видео. Однако принцип мониторинга video.paused имеет прямые практические применения.

Например, вы можете создать сцену с вступительным роликом и автоматически начать игру после его завершения:

function update() {
    if (introVideo.video.paused && introVideo.video.currentTime >= introVideo.video.duration) {
        // Видео завершилось и стоит на паузе
        this.scene.start('Level1');
    }
}

Или управлять фоновыми видео-слоями в зависимости от действий игрока. Например, при приближении к определённой области включать второе видео поверх основного, отслеживая, когда его можно безопасно остановить.

Важно помнить, что доступ к нативному элементу через .video даёт вам полный контроль, но также требует понимания HTML5 Video API.

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

Пример bugs/4910 наглядно демонстрирует важность отслеживания внутреннего состояния видеоэлементов в Phaser. Используя свойство .video.paused в цикле update, вы можете строить сложную логику, зависящую от завершения или паузы в видео. Для экспериментов попробуйте

  1. Изменить логику с бесконечного цикла на однократное воспроизведение и запускать новое видео по завершении предыдущего
  2. Связать паузу видео с паузой в игровом процессе (состояние game.paused)
  3. Создать интерфейс с кнопками, которые ставят конкретное видео на паузу или меняют его прозрачность, основываясь на данных из update