Создание прелоадера в Phaser 3

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

В Phaser, прежде чем вы сможете использовать какие-либо ресурсы, вы должны сначала загрузить их в функцию preload сцены. Если вы загружаете большое количество ресурсов, это может занять некоторое время для загрузки всех ресурсов в игру, и именно здесь предварительный загрузчик действительно имеет значение.

Цель этого руководства - научить вас основам создания экрана предварительной загрузки, создав индикатор выполнения, который будет динамически обновляться по мере загрузки ресурсов игры. Вы можете увидеть итоговый результат ниже: Вы можете скачать все файлы проекта здесь.

Настройка проекта

Чтобы запустить игру Phaser локально, вам понадобится веб-сервер для запуска игры. Если у вас еще его нет, вы можете прочитать, как это сделать в уроке Начало работы с Phaser 3. Вам также понадобится IDE или текстовый редактор для написания кода. Если у вас его еще нет, мы бы рекомендовали редактор Brackets, поскольку он прост в использовании и имеет функцию Live Preview, которая позволит вам запускать игру Phaser без установки веб-сервера.

После того, как вы выполните эти настройки, мы настроим основной код для нашей игры. Откройте вашу IDE и создайте новый файл с именем index.html. Мы собираемся создать простую HTML-страницу, добавить ссылку на Phaser и создать наш игровой объект Phaser. В index.html добавьте следующий код:

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8">
</head>

<body>
    <script src="//cdn.jsdelivr.net/npm/phaser@3.20.1/dist/phaser.js"></script>
    <script type="text/javascript">
        // конфигурация игры для Phaser
        var config = {
            type: Phaser.AUTO,
            parent: 'phaser-example',
            width: 800,
            height: 600,
            scene: {
                preload: preload,
                create: create
            }
        };

        // Создаем новый игровой объект Phaser
        var game = new Phaser.Game(config);

        function preload() {
        }

        function create() {
        }

    </script>
</body>

</html>

Давайте рассмотрим данный код:

  • Мы создали конфигурацию, которая будет использоваться для нашей игры Phaser.
  • В объекте config в поле type мы устанавливаем тип рендерера для нашей игры. Есть два основных типа: Canvas и WebGL. WebGL является более быстрым средством рендеринга и имеет лучшую производительность, но не все браузеры поддерживают его. Значение AUTO заставит Phaser будет использовать WebGL, если он доступен, в противном случае он будет использовать Canvas.
  • В объекте config поле parent используется, чтобы сообщить Phaser, что нужно визуализировать нашу игру в существующий элемент <canvas> с указанным идентификатором, если он существует. Если он не существует, то Phaser создаст для нас элемент <canvas>.
  • В объекте config мы указываем ширину и высоту видимой области нашей игры.
  • В объект config мы добавили объект сцены, который будет использовать созданные нами функции preload и create.
  • Наконец, мы передали наш объект конфигурации в Phaser при создании нового экземпляра игры.

Если вы попытаетесь запустить свою игру, вы увидите черный экран, а если вы откроете консоль в инструментах разработчика, вы увидите лог с версией Phaser, в которой работает ваша игра.

Загрузка ресурсов

Теперь, когда наш проект настроен, мы можем начать. Прежде чем мы сможем создать наш прелоадер, нам нужно загрузить некоторые ресурсы в нашу игру. Для простоты мы собираемся использовать одно изображение и перезагрузить его несколько раз, чтобы имитировать загрузку большого количества ресурсов.

Вам нужно будет поместить изображение в ту же папку, что и файл index.html.

Чтобы загрузить наше изображение и отобразить его в игре, нам необходимо переписать функции preload и create в index.html:

function preload() {
    this.load.image('logo', 'zenvalogo.png');
    for (var i = 0; i < 500; i++) {
        this.load.image('logo'+i, 'zenvalogo.png');
    }
}

function create() {
    var logo = this.add.image(400, 300, 'logo');
}

Если вы перезагрузите свою игру в браузере, вы должны увидеть логотип в вашей игре:

Создание прелоадера

Теперь, когда наши ресурсы загружаются, пришло время создать наш прелоадер. В начало функции preload добавьте следующий код:

this.load.on('progress', function (value) {
    console.log(value);
});

this.load.on('fileprogress', function (file) {
    console.log(file.src);
});

this.load.on('complete', function () {
    console.log('Завершено');
});

Этот код создает несколько прослушивателей событий, которые будут прослушивать события progress , fileprogress и complete, которые отправляются из Phaser LoaderPlugin. События progress и fileprogress будут отправляться каждый раз при загрузке файла, а событие complete будет отправляться только после завершения загрузки всех файлов.

Когда происходит событие progress, вы можете получить значение от 0 до 1, которое можно использовать для отслеживания общего прогресса процесса загрузки. Когда генерируется событие fileprogress, вы также получаете объект, содержащий информацию о только что загруженном файле. Оба этих события могут быть использованы для создания пользовательского прелоадера с предоставленной информацией. Вот пример данных, которые отправляются: Для создания индикатора выполнения мы будем использовать объект GameObject.Graphics. В функции preload добавьте следующий код в начало функции, над кодом, который вы уже добавили:

var progressBar = this.add.graphics();
var progressBox = this.add.graphics();
progressBox.fillStyle(0x222222, 0.8);
progressBox.fillRect(240, 270, 320, 50);

Затем обновите прослушиватель события progress в функции preload следующим кодом:

this.load.on('progress', function (value) {
    console.log(value);
    progressBar.clear();
    progressBar.fillStyle(0xffffff, 1);
    progressBar.fillRect(250, 280, 300 * value, 30);
});

В приведенном выше коде мы создаем два отдельных прямоугольника: progressBar и progressBox. Прямоугольник ProgressBox используется в качестве границы (контейнера) вокруг ProgressBar, а ProgressBar применяется в качестве индикатора загрузки ресурсов в процентах. Мы делаем это, вычисляя ширину прямоугольника на основе значения прогресса, которое мы получаем. Таким образом, каждый раз, когда мы получаем событие progress, мы должны видеть увеличение прямоугольника.

Если вы перезагрузите игру, вы увидите индикатор выполнения, который заполняется при загрузке ресурсов. Однако с этим есть одна проблема. Когда все ресурсы загружены, прелоадер остается на экране, а поверх него загружается изображение логотипа. Чтобы это исправить, мы можем обновить прослушиватель события complete, чтобы уничтожить наш прелоадер после загрузки всех ресурсов.

В обработчике события complete добавьте следующий код ниже console.log() :

progressBar.destroy();
progressBox.destroy();

Теперь, если вы перезагрузите игру, индикатор выполнения должен исчезнуть, прежде чем изображение логотипа отобразится на экране.

Добавление текста

Мы выполнили основную часть нашего прелоадера, но мы можем легко его улучшить, добавив дополнительный текст. Сначала мы добавим простое сообщение «Загрузка…» в загрузчик. В функцию preload добавьте следующий код под строками про progressBox:

var width = this.cameras.main.width;
var height = this.cameras.main.height;
var loadingText = this.make.text({
    x: width / 2,
    y: height / 2 - 50,
    text: 'Загрузка...',
    style: {
        font: '20px monospace',
        fill: '#ffffff'
    }
});
loadingText.setOrigin(0.5, 0.5);

Затем в обработчике событий complete добавьте следующий код:

loadingText.destroy();

Давайте рассмотрим, что мы только что добавили:

  • Мы создали две новые переменные: width и height. Эти переменные получают ширину и высоту текущей видимой области нашей игры.
  • Мы создали новый объект Phaser Text GameObject с названием loadingText. Этот игровой объект использует переменные width и height, которые мы только что создали, и мы устанавливаем стиль и текст по умолчанию для этого игрового объекта.
  • Мы устанавливаем начальную точку для текста в (0,5; 0,5), что поможет разместить по центру наш игровой объект.
  • Наконец, мы обновили обработчик событий complete, чтобы уничтожить данный текст после загрузки всех игровых ресурсов.

Если вы перезагрузите игру, ваш экран должен выглядеть следующим образом: Далее мы собираемся добавить дополнительный текст, который будет отображать процент от полосы загрузки. Для этого нам просто нужно создать еще один текстовый объект игры и обновлять текст, чтобы использовать переменную value, которая отправляется обработчику события progress . В функции preload добавьте следующий код ниже кода про loadingText:

var percentText = this.make.text({
    x: width / 2,
    y: height / 2 - 5,
    text: '0%',
    style: {
        font: '18px monospace',
        fill: '#ffffff'
    }
});
percentText.setOrigin(0.5, 0.5);

Теперь в обработчике события progress добавьте следующий код над кодом про progressBar:

percentText.setText(parseInt(value * 100) + '%');

Наконец, в обработчик события complete добавьте следующий код:

percentText.destroy();

Вот краткое изложение того, что мы только что сделали:

  • Создан новый текстовый объект игры percentText .
  • Мы устанавливаем начало координат (0,5, 0,5), чтобы отцентрировать объект.
  • В обработчике события progress мы обновляем текст объекта каждый раз при загрузке файла. Мы умножаем значение на 100, так как приходящее значение находится в диапазоне между 0 и 1.
  • Наконец, мы обновили обработчик события complete , чтобы уничтожить объект. Если вы перезагрузите свою игру, вы должны увидеть процентное обновление индикатора выполнения загрузки. Теперь, когда индикатор выполнения показывает процент, мы добавим немного текста, чтобы отобразить, какой именно ресурс был загружен. То есть, мы создадим еще один текстовый объект игры и будем обновлять его с помощью файловых данных, отправляемых обработчику события fileprogress. В функцию preload добавьте следующий код ниже кода про percentText:
    var assetText = this.make.text({
    x: width / 2,
    y: height / 2 + 50,
    text: '',
    style: {
        font: '18px monospace',
        fill: '#ffffff'
    }
    });
    assetText.setOrigin(0.5, 0.5);

    Затем в обработчике события fileprogress добавьте следующий код:

    assetText.setText('Загрузка ресурса: ' + file.key);

    Наконец, в обработчик события complete добавьте следующий код:

    assetText.destroy();

    Теперь, если вы перезагрузите свою игру, вы должны увидеть, что текст ресурса обновляется по мере загрузки каждого ресурса. В этом примере мы в итоге вывели ключ ресурса вместо имени файла, т. к. загружаем только одно изображение. Если вы хотите вывести имя файла, вы можете заменить следующую строку:

    assetText.setText('Загрузка ресурса: ' + file.key);

    на

    assetText.setText('Загрузка ресурса: ' + file.src);

    Вот полный index.html:

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8">
</head>

<body>
    <script src="//cdn.jsdelivr.net/npm/phaser@3.20.1/dist/phaser.js"></script>
    <script type="text/javascript">
        // конфигурация игры для Phaser
        var config = {
            type: Phaser.AUTO,
            parent: 'phaser-example',
            width: 800,
            height: 600,
            scene: {
                preload: preload,
                create: create
            }
        };

        // Создаем новый игровой объект Phaser
        var game = new Phaser.Game(config);

        function preload() {
            var progressBar = this.add.graphics();
            var progressBox = this.add.graphics();

            var width = this.cameras.main.width;
            var height = this.cameras.main.height;
            var loadingText = this.make.text({
                x: width / 2,
                y: height / 2 - 50,
                text: 'Загрузка...',
                style: {
                    font: '20px monospace',
                   fill: '#ffffff'
                }
            });
            loadingText.setOrigin(0.5, 0.5);

            var percentText = this.make.text({
                x: width / 2,
                y: height / 2 - 5,
                text: '0%',
                style: {
                    font: '18px monospace',
                    fill: '#ffffff'
                }
            });
            percentText.setOrigin(0.5, 0.5);

            var assetText = this.make.text({
                x: width / 2,
                y: height / 2 + 50,
                text: '',
                style: {
                    font: '18px monospace',
                    fill: '#ffffff'
                }
            });
            assetText.setOrigin(0.5, 0.5);

            progressBox.fillStyle(0x222222, 0.8);
            progressBox.fillRect(240, 270, 320, 50);

            this.load.on('progress', function (value) {
                console.log(value);
                percentText.setText(parseInt(value * 100) + '%');
                progressBar.clear();
                progressBar.fillStyle(0xffffff, 1);
                progressBar.fillRect(250, 280, 300 * value, 30);
            });

            this.load.on('fileprogress', function (file) {
                console.log(file.src);
                assetText.setText('Загрузка ресурса: ' + file.key);
            });

            this.load.on('complete', function () {
                console.log('Завершено');
                progressBar.destroy();
                progressBox.destroy();
                loadingText.destroy();
                percentText.destroy();
                assetText.destroy();
            });

            this.load.image('logo', 'logoyocton.png');
            for (var i = 0; i < 500; i++) {
                this.load.image('logo'+i, 'logoyocton.png');
            }
        }

        function create() {
            var logo = this.add.image(400, 300, 'logo');
        }

    </script>
</body>

</html>

Вы можете скачать готовый пример здесь.

Заключение Теперь, когда текст ресурса отображается, этот учебник подошел к концу. Как видите, добавление прелоадера в вашу игру является отличным решением, когда вы загружаете большое количество ресурсов и хотите, чтобы игроки были в курсе текущего состояния игры. С помощью Phaser действительно легко добавить простой прелоадер, а вы можете легко расширить данный пример, чтобы добавить необходимый функционал.