Примечание: ниже находится перевод статьи "Embedding and Encoding in JavaScript", в которой автор (JavaScript-евангелист в Mozilla и автор библиотеки jQuery по совместительству) рассматривает способы сжатия информации и ее объединения при помощи JavaScript и некоторых других методов. Мои комментарии далее курсивом.
Грубая реализация на JavaScript (заметка на Хабре) первого уровня Super Mario Brothers буквально на днях обошла весь Интернет. В нее, в общем, можно играть, хотя упущены многие ключевые аспекты (нет грибов, нет флага, нет повышающих очков и т.д.). Однако, это, на самом деле, не самый интересный аспект в этой игре.
Наверное, больше всего интереса в этом веб-приложении представляет не сама игровая механика, а тот факт, что все собрано в одном-единственном файле — игровом скрипте, который включает все игровые спрайты и всю музыку.
Это весьма любопытный образец инженерного искусства, и он содержит вещи, которые вы сможете применить в своем собственном коде. Давайте двинемся дальше и разберем, как это было сделано, чтобы мы смогли бы использовать все эти приемы для наших собственных сайтов.
Удивительно, но способ подключения музыки в этом приложении, пожалуй, наиболее простой для понимания. Приложение использует схему (протокол) data:URL, который кодирует музыкальные MIDI-файлы Mario при помощи base64.
Результат очень просто найти в исходном файле:
aSounds = [ // очень маленькая и простая тема Mario. Написал Mike Martel. "data:audio/mid;base64,TVRoZAAAAAYAAQAEAMBNVH...", // игра закончилась. Написал John N. Engelmann. "data:audio/mid;base64,TVRoZAAAAAYAAQADAHhNVH..." ],
data:URL
работает следующим образом: все содержимое выбранного файла кодируется в виде одной строки в исходном файле. В результате, мы получаем 1 файл вместо двух. Это также можно использовать (и используется гораздо чаще, чем кодирование музыкальных фрагментов) для включения картинок (PNG, JPG, GIF и т.д.). В данном случае вся закодированная строка отправляется элементу <embed/>
, который и отвечает за проигрывание midi-файла. Мы можем обнаружить конечный результат этих махинаций в следующем виде:
playMusic = function(iSoundID, bLoop) { if (!bMusic) return; var oEmbed = dc("embed"); oEmbed.src = aSounds[iSoundID]; oEmbed.id = "sound_" + iSoundID; if (bLoop) oEmbed.setAttribute("loop", "true"); oEmbed.setAttribute("autostart", "true"); oEmbed.style.position = "absolute"; oEmbed.style.left = -1000; appChild(document.body, oEmbed); },
Вышеприведенный код просто создает элемент embed
, устанавливает для него автозапуск проигрывания музыки и в качестве источника данных указывает data:url
. В итоге мы получаем MIDI-файл, который сам начинает проигрываться (предположительно, тот же результат мог быть получен при использовании другого универсального музыкального файлового формата — WAV).
Файлы в формате data:URL
могут проигрываться всеми браузерами, за исключением Internet Explorer (эта техника представлена в Internet Explorer 8). В этом случае пользователи Internet Explorer просто не получает музыкального сопровождения для игры (хотя, я думаю, можно было бы только для IE и подгружать его, создавая тот же самый embed
просто со ссылкой на внешний файл).
Вторая, наиболее интересная, часть техник применяется для вставки различной графики (спрайтов), которая используется в игре. Это относится к самому персонажу Mario, и к блокам, трубам и даже фоновым изображениям.
Веб-приложение использует ряд весьма занимательных методов, для того чтобы уменьшить результирующий размер изображений. Вместо уже известного протокола data:url
(который в данном случае совершенно уместен и мог быть использован) оно реализует вывод графики с помощью элемента <canvas/>
(и набора обычных div
/span
для Internet Explorer). При ориентировании на эти две платформы нужно иметь в виду, что требуется разработать способ, при которой можно хранить и получать отдельные пиксели для спрайтов.
В результате, для достижения поставленной цели нам нужно хранить все эти пиксели весьма эффективным методом, который предполагает малый размер итогового файла и, как это ни странно, простой способ их получения.
Для начала, все доступные цвета должны быть пересчитаны (mapped out) и минимизированы. Все спрайты в игре имеют не более 4 цветов (некоторые даже меньше). Мы можем схематично изобразить этот процесс следующим образом:
Создание карты изображений
В результате этого процесса по уменьшению размера каждого спрайта путем представления его в бинарном виде (имеется в виду, что на 1 точку приходится не 1 байт, как в BMP-формате на 256 цветов или GIF-формате, а гораздо меньше, от 1 до 2 битов, что позволяет сократить общий размер изображений до 8 раз). Каждый спрайт уменьшается до бинарного представления соответствующих цветов. Следующим шагом стало форматирование этого двоичного представления в строку, которую было бы легко передавать, примерно следующим образом:
Строка для бинарного вида карты изображений
В результате мы имеем представление каждого спрайта в виде строки, которое является просто феноменально малым (в пересчете на байты). При этом данная строка может быть конвертирована обратно в исходное графическое представление.
Описанная техника по кодированию и уменьшению размера изображений напоминает подход, используемый для GIF-формата, с той лишь разницей, что для каждого цвета используется минимально возможная длина кодирующей последовательности в битах. PNG-формат и его base64-представление (как в случае с MIDI-файлами) не дало бы такого эффекта, потому что размер изображения тогда увеличился бы на 30%. Хотя, мне кажется, что использование фильтров из PNG-подхода для такого кодирования позволило бы сжать изображение еще лучше (например, задав цвет и число в непрерывной линии, которые имеют этот цвет, т.е. сжав «полоски» одного цвета до суммы представления цвета и длины полоски).
Обе кодирующие техники могут оказаться весьма полезными для использования в вашем JavaScript-коде — особенно, если вы стремитесь объединить как можно больше данных в самом исходном коде, а не в других внешних файлах, минимизируя, тем самым, число запросов к серверу и увеличивая скорость загрузки вашего приложения.