WebGL: перевод игры с мобильной платформы на десктопную

    Создание десктопной версии мобильной игры стало настоящей исследовательской миссией для краснодарской студии Plarium. В этой статье мы расскажем, как перешли на технологию WebGL при переносе проекта Vikings: War of Clans на новую платформу.

    image

    Освоение WebGL


    Игра Vikings: War of Clans создавалась на кроссплатформенном движке Unity 3D, который позволяет запускать приложение более чем на 25 платформах: от мобильного устройства до браузера. У нас уже был опыт работы с Adobe Flash, но для этого проекта мы выбрали технологию WebGL, и вот почему:

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

    Стоит отметить, что разработчики Unity предоставляют качественную техническую поддержку и развернутую документацию. При работе над проектом мы получали всю необходимую информацию об ограничениях для WebGL в руководстве пользователя и на форумах, посвященных этому движку, так что без помощи вы точно не останетесь.

    Трудности перевода


    Для того чтобы собрать WebGL-билд, движок Unity проделывает сложную цепочку действий: используя Mono C# compiler, код игры преобразуется в промежуточный (IL) код, а затем с помощью технологии IL2CPP получается код на языке C++, который в свою очередь отдает нужный нам WebGL-проект через компилятор Emscripten. В результате не все возможности .Net, предоставляемые движком Unity 3D, доступны в окончательной сборке. Например, не поддерживается многопоточность, не работает System.Net.Sockets и т. д.

    Для полноценного перехода к веб-версии нам предстояло устранить ряд проблем.

    Реализация WebSockets. Для сетевого взаимодействия была написана обертка, которая с минимальными изменениями позволила нам работать с сервером через веб-сокеты. Простой пример доступен в Unity Asset Store.

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

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

    OpenURL: function(url){
         window.open(Pointer_stringify(url));
    }
    

    Так удалось добиться открытия всех необходимых окон. О создании плагинов под WebGL написано в официальной документации и в блоге разработчиков.

    Шрифты. В определенный момент возникла потребность доработать отображение символов. В частности, ввод иероглифов должен осуществляться так: пользователь набирает комбинации латинских букв, которые преобразуются в японские, китайские или корейские письменные знаки. В самом Unity этот способ не предусмотрен, зато такой функционал есть в браузерах. Мы реализовали его с помощью отдельного плагина. Кроме того, WebGL, в отличие от нативных приложений и того же Flash, не имеет доступа к системным шрифтам. Поэтому мы добавляли их непосредственно в проект, что, конечно, увеличивало размер сборки.

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

    В нашей игре существуют несколько атласов для текстур, и, как правило, мы используем DXT1- и DXT5-компрессии (в зависимости от наличия или отсутствия в них альфа-канала). Исключением являются два атласа: содержащий текстуры, которые крайне плохо реагируют на сжатие (текстуры с градиентом, кнопки с цифрами или буквами) и атлас для emoji-символов. В этих случаях мы используем RGBA32.

    Для уменьшения размеров всех остальных текстур, находящихся вне атласов, подходят форматы DXT1Crunched и DXT5Crunched. На данный момент мы переопределили метод OnPreprocessTexture у AssetPostprocessor, что позволило в автоматическом режиме применить эти настройки для всех текстур в проекте.

    Сторонние сервисы. Будьте готовы к тому, что сторонние библиотеки, которые эффективно используются в мобильной игре, могут некорректно работать или даже не работать вовсе при сборке WebGL-проекта. В такой ситуации лучший выход — найти альтернативу. Например, для авторизации на Facebook мы дополнительно применяли нативный плагин JavaScript. Он позволяет идентифицировать пользователя уже во время загрузки страницы и заранее получить разрешения (permissions), требуемые для игры.

    Переход от Unity 5.2 к 5.6. В процессе разработки у нас возникла необходимость перехода от версии Unity 5.2 к версии 5.6, в которой добавился Unity Loader и было исправлено несколько критических проблем (например, падение билда по памяти при перезагрузке страницы). Эти изменения существенно упростили инициализацию приложения. Вы можете ознакомиться с кодом загрузки билдов обеих версий ниже.

    Unity 5.2:

    var canvas = document.createElement("canvas");
                canvas.style.width = "100%";
                canvas.style.height = "100%";
                canvas.addEventListener("contextmenu", function(e)
                    {
                        e.preventDefault()
                    }),
                    canvas.id = "canvas";
                gameContainer.appendChild(canvas);
    
          if (consts.isDebug)
          {
               createScript('unity52/module_pre_debug.js?v=' + consts.appVersion);
               document.WebExistsGLCallback = function()
               {
                    Module.postRun.push(unityHelper.postRun);
                    createScript('Debug/Release/fileloader.js?v=' + consts.appVersion);
                    createScript('unity52/module_post_debug.js?v=' + consts.appVersion);
                    tracker.load();
               };
               createScript('unity52/UnityConfig.js?v=' + consts.appVersion);
         }
                else
         {
              createScript('unity52/module_pre.js?v=' + consts.appVersion);
              document.WebExistsGLCallback = function()
         {
              Module.postRun.push(unityHelper.postRun);
              createScript('Release/fileloader.js?v=' + consts.appVersion);
              createScript('unity52/module_post.js?v=' + consts.appVersion);
              tracker.load();
          };
          createScript('unity52/UnityConfig.js?v=' + consts.appVersion);
    }

    Unity 5.6:

    
    unityHelper.url = consts.isDebug ? "Debug/Build/Debug.json" : "Build/WebGL.json";
    Module = UnityLoader.instantiate(gameContainer, unityHelper.url,
    {
         onProgress: function(a, b)
        {
             preloader.setProgress(b);
        },
        Module:
        {
             resolveBuildUrl: function(buildUrl)
             {
                  return (buildUrl.match("/(http|https|ftp|file):\/\//") ? buildUrl :unityHelper.url.substring(0, unityHelper.url.lastIndexOf("/") + 1) + buildUrl) + "?v=" + consts.appVersion;
             },
             postRun: [unityHelper.postRun],
        },
    }).Module;

    Новый опыт для новых проектов


    На реализацию десктопной версии Vikings: War of Clans ушло полгода. Большую часть времени мы создавали новый UI и около двух месяцев проводили технические работы для перехода на WebGL.

    Принимаясь за Throne: Kingdom at War, мы уже понимали, что предстоит сделать и в какие сроки сможем уложиться. Благодаря этому второй проект перешел на веб-платформу быстрее — всего за 4 месяца. Отметим, что в обоих случаях мобильные игры расширяли свой функционал, пока их десктопные версии приближались к выходу в продакшн, поэтому отдельные элементы UI корректировались уже в процессе работы.

    image

    Советы разработчикам


    • Разделите логику и представление в архитектуре вашего приложения, а также постарайтесь выделить единую кодовую базу для проекта. Это поможет избежать проблемы дублирования кода и облегчит поддержку всех платформ, которые вы используете.
    • В Unity WebGL работа с памятью отличается от других платформ. Изначально необходимо указать WebGLMemorySize в player settings (по умолчанию — 256 MB). Размер выделяемой памяти должен быть известен заранее, чтобы браузер мог выделить пространство для вашего приложения. После этого вы уже не сможете изменить размер буфера. Зачастую контент потребляет большое количество памяти, и, чтобы определить ее оптимальный объем, вам придется тестировать приложение. Лучше всего сделать это с помощью Unity Profiler.
    • При разработке WebGL-проекта вы неизбежно столкнетесь с шаблонами. Unity по умолчанию предоставляет простую HTML-страницу с прогресс-баром для загрузки вашего приложения, а также дает возможность реализовать свои кастомные шаблоны. Если в вашем приложении используется внутренний прелоадер (для загрузки ресурсов, подключения к серверу и т. д.), то для избежания поочередного отображения двух прелоадеров лучше ограничиться одним, который доступен в шаблоне. После загрузки вашего приложения через Unity Loader стоит отсылать события (events) о процессе загрузки в JS-методы шаблона, скрыв на время слой шаблона с приложением. Таким образом вы избежите дублирования текстур прелоадера.

    Мы надеемся, что опыт студии Plarium-South откроет новые возможности для ваших проектов.
    • +10
    • 3,5k
    • 2
    Plarium 90,54
    Разработчик мобильных и браузерных игр
    Поделиться публикацией
    Похожие публикации

    Вакансии компании Plarium

    • Краснодар Полный рабочий день
    • Краснодар Полный рабочий день
    • Краснодар Полный рабочий день
    Комментарии 2
    • 0

      Спасибо, но немного поверхностно.
      Гораздо интересней почитать, например, как интегрировали Facebook login или как дебажили webgl, как контролируете кэш при обновлении?

      • 0
        Cпасибо за комментарий.

        Интеграция Facebook SDK
        Для интеграции FB SDK в темплейте нам не нужно было добавлять какие-либо
        файлы, достаточно просто добавить метод загрузки SDK в JS код темплейта.

        function(d, s, id)
        {
        var js, fjs = d.getElementsByTagName(s)[0];
        if (d.getElementById(id))
        {
        return;
        }
        js = d.createElement(s);
        js.id = id;
        if (consts.isDebug) js.src = "https://connect.facebook.net/en_US/sdk/debug.js#xfbml=1&version=v2.5&appId=" + consts.appId + "&cookie=1&status=1";
        else js.src = "https://connect.facebook.net/en_US/sdk.js#xfbml=1&version=v2.5&appId=" + consts.appId + "&cookie=1&status=1";
        fjs.parentNode.insertBefore(js, fjs);
        }(document, 'script', 'facebook-jssdk'));


        После инстанциации SDK и проверки логин статуса мы делаем запрос
        необходимых нам разрешений (permissions). В случае отказа пользователем
        предоставить разрешения, он не может продолжить загрузку, в центре экрана появляется кнопка “Play”, при нажатии на которую идет повторный запрос разрешений.

        Так как приложение уже залогинено, нам не нужно это делать повторно в самой игре.
        Достаточно просто вызвать FB.Init у C# версии FB SDK и мы можем использовать необходимый нам функционал API.

        WebGL Debugging
        Для тестов мы собираем два билда: debug и release. Их отличие лишь в том, что первый
        собирается с включенным флагом “exceptionSupport”, который дает возможность увидеть расширенную информацию об ошибках, но снижает производительность самой игры. Все основные ситуации мы воспроизводим и дебажим непосредственно в Unity.

        Контроль кэша
        При каждом обновлении релиз-версии игры мы инкрементируем значение константы с версией игры и подставляем ее в качестве аргумента в ссылку на файлы билда ( + "?v=" + consts.appVersion). Так как JS файлы шаблона не изменяются при каждом билде, мы просто инкрементируем в ссылке число (main.js?v=25) в случае внесения изменений в файл.

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

      Самое читаемое