Простое управление ресурсами для игр HTML5

Введение

HTML5 предоставляет множество полезных API-интерфейсов для создания современных, адаптивных и мощных веб-приложений в браузере. Это здорово, но вы действительно хотите создавать игры и играть в них! К счастью, HTML5 также открыл новую эру разработки игр, в которой используются API-интерфейсы, такие как Canvas, и мощные движки JavaScript, позволяющие доставлять игры прямо в ваш браузер без необходимости использования плагинов.

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

Проблема

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

Основным способом программной загрузки изображения в веб-браузер является следующий код:

var image = new Image();
image.addEventListener("success", function(e) {
  // do stuff with the image
});
image.src = "/some/image.png";

Теперь представьте, что у вас есть сотня изображений, которые необходимо загрузить и отобразить при запуске игры. Как узнать, что все 100 изображений готовы? Все ли они успешно загрузились? Когда игра должна начаться?

Решение

Позвольте менеджеру активов управлять очередями активов и сообщать игре, когда все будет готово. Менеджер активов обобщает логику загрузки активов по сети и обеспечивает простой способ проверки статуса.

Наш простой менеджер активов предъявляет следующие требования:

  • ставить в очередь загрузки
  • начать загрузку
  • отслеживать успехи и неудачи
  • сигнализировать, когда все будет сделано
  • легкий возврат активов

Очередь

Первое требование — поставить загрузки в очередь. Этот дизайн позволяет вам объявлять нужные вам ресурсы, не загружая их. Это может быть полезно, например, если вы хотите объявить все ресурсы для игрового уровня в файле конфигурации.

Код конструктора и организации очереди выглядит так:

function AssetManager() {
  this.downloadQueue = [];
}

AssetManager.prototype.queueDownload = function(path) {
    this.downloadQueue.push(path);
}

Начать загрузку

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

К счастью, веб-браузер может распараллеливать загрузки — обычно до 4 подключений на хост. Один из способов ускорить загрузку ресурсов — использовать диапазон доменных имен для хостинга ресурсов. Например, вместо того, чтобы обслуживать все данные с assets.example.com, попробуйте использовать assets1.example.com, assets2.example.com, assets3.example.com и т. д. Даже если каждое из этих доменных имен представляет собой просто CNAME одного и того же веб-сервера, веб-браузер видит их как отдельные серверы и увеличивает количество соединений, используемых для загрузки ресурсов. Узнайте больше об этом методе из раздела «Распределение компонентов по доменам» в разделе «Лучшие практики по ускорению вашего веб-сайта».

Наш метод инициализации загрузки называется downloadAll() . Мы нарастим его со временем. На данный момент вот первая логика, позволяющая просто начать загрузку.

AssetManager.prototype.downloadAll = function() {
    for (var i = 0; i < this.downloadQueue.length; i++) {
        var path = this.downloadQueue[i];
        var img = new Image();
        var that = this;
        img.addEventListener("load", function() {
            // coming soon
        }, false);
        img.src = path;
    }
}

Как вы можете видеть в приведенном выше коде, downloadAll() просто проходит по очереди загрузки и создает новый объект Image. Добавляется прослушиватель событий для события загрузки и устанавливается источник изображения, который запускает фактическую загрузку.

С помощью этого метода вы можете начать загрузку.

Отслеживание успехов и неудач

Еще одно требование — отслеживать как успехи, так и неудачи, потому что, к сожалению, не всегда все получается идеально. Пока код отслеживает только успешно загруженные ресурсы. Добавив прослушиватель событий для ошибок, вы сможете фиксировать как успешные, так и неудачные сценарии.

AssetManager.prototype.downloadAll = function(downloadCallback) {
  for (var i = 0; i < this.downloadQueue.length; i++) {
    var path = this.downloadQueue[i];
    var img = new Image();
    var that = this;
    img.addEventListener("load", function() {
        // coming soon
    }, false);
    img.addEventListener("error", function() {
        // coming soon
    }, false);
    img.src = path;
  }
}

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

Сначала мы добавим счетчики к объекту в конструкторе, который теперь выглядит так:

function AssetManager() {
<span class="highlight">    this.successCount = 0;
    this.errorCount = 0;</span>
    this.downloadQueue = [];
}

Затем увеличьте счетчики в прослушивателях событий, которые теперь выглядят следующим образом:

img.addEventListener("load", function() {
    <span class="highlight">that.successCount += 1;</span>
}, false);
img.addEventListener("error", function() {
    <span class="highlight">that.errorCount += 1;</span>
}, false);

Менеджер активов теперь отслеживает как успешно загруженные, так и неудачные активы.

Сигнализация завершения

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

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

AssetManager.prototype.isDone = function() {
    return (this.downloadQueue.length == this.successCount + this.errorCount);
}

Сравнивая SuccessCount + errorCount с размером DownloadQueue, менеджер ресурсов узнает, завершился ли каждый ресурс успешно или имел какую-то ошибку.

Конечно, знать, сделано ли это, — это только полдела; управляющему активами также необходимо проверить этот метод. Мы добавим эту проверку в оба наших обработчика событий, как показано в коде ниже:

img.addEventListener("load", function() {
    console.log(this.src + ' is loaded');
    that.successCount += 1;
    if (that.isDone()) {
        // ???
    }
}, false);
img.addEventListener("error", function() {
    that.errorCount += 1;
if (that.isDone()) {
        // ???
    }
}, false);

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

Если менеджер активов завершил загрузку всех ресурсов, мы, конечно же, вызовем метод обратного вызова! Давайте изменим downloadAll() и добавим параметр для обратного вызова:

AssetManager.prototype.downloadAll = function(downloadCallback) {
    ...

Мы вызовем метод downloadCallback внутри наших прослушивателей событий:

img.addEventListener("load", function() {
    that.successCount += 1;
    if (that.isDone()) {
        downloadCallback();
    }
}, false);
img.addEventListener("error", function() {
    that.errorCount += 1;
    if (that.isDone()) {
        downloadCallback();
    }
}, false);

Управляющий активами наконец готов выполнить последнее требование.

Простое получение активов

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

Наше последнее требование подразумевает какой-то метод getAsset, поэтому мы добавим его сейчас:

AssetManager.prototype.getAsset = function(path) {
    return this.cache[path];
}

Этот объект кэша инициализируется в конструкторе, который теперь выглядит так:

function AssetManager() {
    this.successCount = 0;
    this.errorCount = 0;
    this.cache = {};
    this.downloadQueue = [];
}

Кэш заполняется в конце downloadAll() , как показано ниже:

AssetManager.prototype.downloadAll = function(downloadCallback) {
  ...
      img.addEventListener("error", function() {
          that.errorCount += 1;
          if (that.isDone()) {
              downloadCallback();
          }
      }, false);
      img.src = path;
      <span class="highlight">this.cache[path] = img;</span>
  }
}

Бонус: исправление ошибок.

Вы заметили ошибку? Как написано выше, метод isDone вызывается только при возникновении событий загрузки или ошибки. Но что, если у менеджера активов нет ресурсов в очереди на загрузку? Метод isDone никогда не срабатывает, и игра никогда не запускается.

Вы можете реализовать этот сценарий, добавив следующий код в downloadAll() :

AssetManager.prototype.downloadAll = function(downloadCallback) {
    if (this.downloadQueue.length === 0) {
      downloadCallback();
  }
 ...

Если в очереди нет активов, обратный вызов вызывается немедленно. Ошибка в программе исправлена!

Пример использования

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

var ASSET_MANAGER = new AssetManager();

ASSET_MANAGER.queueDownload('img/earth.png');

ASSET_MANAGER.downloadAll(function() {
    var sprite = ASSET_MANAGER.getAsset('img/earth.png');
    ctx.drawImage(sprite, x - sprite.width/2, y - sprite.height/2);
});

Приведенный выше код иллюстрирует:

  1. Создает нового управляющего активами
  2. Поставьте ресурсы в очередь для загрузки
  3. Запустите загрузку с помощью downloadAll()
  4. Сигнализируйте о готовности ресурсов, вызывая функцию обратного вызова.
  5. Получить ресурсы с помощью getAsset()

Области для улучшения

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

  • сигнализация, в каком активе произошла ошибка
  • обратные вызовы для указания прогресса
  • получение ресурсов из API файловой системы

Пожалуйста, публикуйте улучшения, вилки и ссылки на код в комментариях ниже.

Полный исходный код

Исходный код этого менеджера активов и игры, из которой он создан, имеют открытый исходный код под лицензией Apache, и их можно найти в учетной записи Bad Aliens на GitHub . В игру Bad Aliens можно играть в браузере, совместимом с HTML5. Эта игра стала темой моего доклада на Google IO под названием Super Browser 2 Turbo HD Remix: введение в разработку игр на HTML5 ( слайды , видео ).

Краткое содержание

В большинстве игр есть какой-то менеджер ресурсов, но для игр HTML5 требуется менеджер ресурсов, который загружает ресурсы по сети и обрабатывает сбои. В этой статье описан простой менеджер ресурсов, который вам будет легко использовать и адаптировать для вашей следующей HTML5-игры. Удачи и, пожалуйста, дайте нам знать, что вы думаете, в комментариях ниже. Спасибо!