webgl unity что это
Создаем игру для WebGL с помощью Unity 5 и JavaScript
Unity – это кроссплатформенный игровой движок, позволяющий создавать игры для ПК, консолей, мобильный устройств и веб-сайтов. Последняя версия движка (Unity 5) имеет возможность экспорта в WebGL, благодаря чему разработчики могут без труда публиковать свои игры в вебе. Как следует из названия, данный экспортер использует WebGL – JavaScript API для рендеринга интерактивной компьютерной 3D графики, а также asm.js – подмножество JavaScript, которое было разработано компанией Mozilla и преподносилось как «язык ассемблера для веба». Больше информации о Asm.js и WebGL для Unity и Unreal Engine доступно здесь.
В этом уроке я хочу показать, как настроить работу с Unity. А также продемонстрирую, как создать простую игру на Unity с помощью JavaScript и выполнить ее экспорт для веба.
По окончании урока у вас получится вот такая игра (для просмотра потребуется браузер с поддержкой WebGL). Проект также доступен для скачивания из репозитория на GitHub.
Несколько слов о JavaScript в Unity
Говоря о JavaScript в Unity, мы подразумеваем нечто вроде диалекта JS под названием UnityScript. И хотя сами специалисты из Unity называют этот язык JavaScript, более скептично настроенные пользователи интернета полагают, что поддержка JavaScript в Unity – это маркетинговая уловка. Так или иначе, нужно сразу сделать оговорку, что UnityScript не соответствует спецификации ECMAScript, и никто даже не делает попытки устранить эти несоответствия.
Устанавливаем Unity
Для начала нам нужна рабочая версия Unity, скачать ее можно здесь. Файлы установки доступны для Windows и для Mac OS X. Пользователи Linux могут запустить Unity с помощью Wine или использовать другой удобный способ.
После установки можем приступать. Откроем Unity и создадим новый 3D проект.
Настраиваем проект
Теперь, когда программа открылась, давайте кратко пройдемся по основному интерфейсу:
Создаем героя
Герой в нашей игре будет прыгать вверх с одной платформы на другую. Если ему не удастся вовремя заскочить на одну из них, он упадет в пропасть и проиграет. Мы начнем с создания героя. Поскольку игра будет от первого лица, внешность героя не имеет никакого значения, и мы можем использовать вместо него стандартную сферу. Плюс в том, что сфера быстро создается, и ее физические характеристики отлично подходят для прыжков по платформам. Добавим ее, выбрав Create в панели Hierarchy и отредактировав следующие параметры во вкладке Inspector:
Нажмем кнопку Play, чтобы проверить результат. На экране должно появиться 3D пространство со сферой на фоне горизонта.
Чтобы сфера падала, у нее должен быть вес. Выходит, нам нужно добавить к ней новый компонент, нажав в панели Inspector кнопку Add Component и выбрав Rigidbody. Так как мы не хотим, чтобы сфера вращалась, нужно зафиксировать ее положение с помощью компонента Rigidbody. Для этого откройте Constraints и выберите все оси в строке Rotation. Проиграйте сцену еще раз и вы увидите, что теперь сфера падает.
Чтобы предотвратить бесконечное падение сферы, создадим нечто вроде платформы. Для этого добавим плоский куб со значением Scale.Y, равным 0.1. Проиграйте сцену еще раз и убедитесь, что сфера успешно приземляется на платформу. Но, стоит отметить, всё это выглядит не очень естественно. Как же заставить сферу подпрыгивать? Для этого нам пригодятся физические материалы.
Наделяем сферу физическими свойствами
Прежде всего, создадим для нашей сферы физический материал, который позволит ей отпрыгивать от поверхности соприкосновения. Для этого нужно создать в папке Assets новую подпапку Materials. Внутри этой подпапки создадим новый физический материал и назовем его Bouncy_Sphere. Вот значения, которые мы должны указать в панели Inspector:
Dynamic Friction: 10
Static Friction: 10
Bounciness: 1
Friction Combine: Maximum
Bounce Combine: Maximum
Если мы добавим этот материал в Sphere Collider, сфера будет подпрыгивать, но всегда на одинаковую высоту. Чтобы она подпрыгивала с каждым разом выше, нужно добавить физический материал и для платформы. Создадим еще один материал под названием Bouncy_Platform и применим к нему следующие значения:
Dynamic Friction: 0.9
Static Friction: 0.9
Bounciness: 1
Friction Combine: Average
Bounce Combine: Multiply
Чтобы не запутаться, переименуем наш плоский куб-платформу на Platform, кликнув по нему дважды в панели Hierarchy. Теперь, начав игру, вы увидите, что сфера с каждым разом подпрыгивает выше.
Также создадим новый стандартный материал под названием Platform, чтобы появилась возможность дать платформе какой-нибудь цвет. Создав его, вставьте цвет #C8FF00 напротив значения Albedo, а затем перетащите этот материал на элемент платформы. Теперь платформа должна стать желтой.
Добавляем вид от первого лица
Для этого нужно в панели Hierarchy перетащить камеру на сферу. В результате камера станет дочерним элементом сферы и будет двигаться вместе со сферой. Камере также необходимо задать некоторые дополнительные настройки:
Помимо этого добавим фонарь в качестве второго дочернего элемента сферы. Он поможет игроку иметь представление о высоте прыжка сферы в любой момент игры. Параметры фонаря выглядят следующим образом:
Настраиваем управление в игре
Наша цель – использовать мышь или трекпад, чтобы позволить игроку двигать сферу в определенном направлении. С этой целью мы напишем первый скрипт. Как и в случае с Rigidbody, скрипт добавляется к элементу игры в качестве компонента. В нашем примере мы добавим JS-скрипт под названием InputController к камере. Так же, как мы это делали со сценой и материалами, создадим в панели Project новую папку под названием Scripts, в которой и будет находиться наш скрипт. Двойной клик по новому скрипту откроет стандартный для Unity редактор MonoDevelop. Его можно заменить на любой другой редактор (Unity > Preferences > External Tools), но сейчас это не имеет никакого значения.
Как видно, в скрипте уже имеется какой-то код. Прежде всего, создадим несколько переменных под первой строкой с текстом #pragma strict (включает принужденное определение типов в Unity):
Первой идет общедоступная переменная, принадлежащая к типу GameObject. Она обозначает сферу. Вернемся к Unity, по прежнему оставив камеру выбранной. Мы увидим, что эта общедоступная переменная находится рядом с пустым полем для ввода. Перетащим сферу на данный участок, таким образом присвоив значение этой переменной.
Две остальные переменные – закрытые, им будут присвоены значения в функции Start. Эта функция вызывается всего один раз, после запуска сцены. Обоим закрытым переменным будет присвоена половина ширины и высоты экрана соответственно. Для этого мы используем встроенный класс Screen:
Единственное, что осталось реализовать в скрипте InputController – получение данных о положении и передвижении мышки. Для этого мы воспользуемся функцией Update, которая вызывается для каждого кадра:
Каждая из двух новых переменных x и z обозначает соответствующую ось. Когда мы смотрим вдоль оси y, мы видим горизонтальную ось x и вертикальную ось z. Мы будем менять положение сферы на этих осях в зависимости от данных, полученных с мышки. Нам понадобится статическая переменная Input.mousePosition, возвращающая двухмерный вектор. Вектор, нулевое значение которого приходится на нижний левый угол, должен переместиться в нашей системе координат на середину экрана. Следующий фрагмент кода демонстрирует такую трансформацию координат. Наконец, вызовем функцию setHeroPosition с обоими рассчитанными значениями в качестве аргументов. Мы запишем эту функцию в новый скрипт HeroController, привязанный к сфере:
Проверим, как работает наш код: подвигаем мышку или трекпад, чтобы сфера упала с платформы.
Реализуем процедурное создание платформ
Для автоматического создания платформ нам нужно нечто вроде шаблона платформы. В Unity такие шаблоны называются префабами. Чтобы создать префаб, необходимо перетащить платформу из панели Hierarchy в новую подпапку Prefabs папки assets. Префабы легко распознать в панели Hierarchy по голубому цвету. Все платформы, за исключением первой, будут создаваться с помощью нового скрипта GameManager, привязанного к камере. Сначала в скрипте обратимся к необходимым переменным:
Нужно обратиться к панели префаба и сфере, потому нужно перетащить их на соответствующе участки редактора. Мы также создаем три закрытых переменных, которые будут использоваться для создания экземпляра панели префаба.
Затем добавим код для получения положения следующей панели. Поместим этот код в дополнительную функцию, чтобы сохранить общую читабельность:
Добавляем меню игры
На данном этапе игрок может прыгать по платформам выше и выше, двигая мышку в нужном направлении. Но если он промахнется, то будет бесконечно падать вниз. Нужно это исправить. Отныне, если сфера упадет ниже первой платформы, на экране будет появляться новая сцена.
Для отслеживания состояния игры мы воспользуемся новой функцией:
В данном случае мы используем класс Application, позволяющий с помощью метода LoadLevel загрузить новую сцену Menu. Для этого сначала создадим сцену, выбрав File > New Scene, и сохраним ее под названием Menu. Затем нам нужно добавить обе сцены в процесс сборки. Настройки сборки доступны во вкладке File > Build Settings. Не закрывая сцену с меню, нажмите кнопку Add Current и добавьте сцену в настройки сборки. Повторите то же действие с открытой сценой уровня. Теперь по завершении игры у вас на экране будет появляться только что созданная сцена с меню игры.
Добавляем кнопку для начала игры
Чтобы в игру можно было поиграть, нужно добавить в меню кнопку для ее запуска. Потому вернемся к настройкам сцены меню игры и изменим прежде всего параметры камеры в панели Inspector:
Clear Flags: Solid Color
Background: #000
Width: 200
Height: 60
Чтобы добавить кнопку, мы воспользуемся элементами пользовательского интерфейса Unity, которые могут быть добавлены как 3D элементы через панель Hierarchy. После добавления кнопки интерфейса в Hierarchy должны появиться следующие элементы: EventSystem и Canvas вместе с дочерним элементом Button и его дочерним элементом Text.
Canvas – это контейнер для всех элементов интерфейса, его можно сделать в некотором смысле адаптивным. Для этого нужно в панели Inspector переключить настройку Canvas Scaler: UI Scale Mode с Constant Pixel Size на Scale With Screen Size. Теперь можно менять положение кнопки:
Убрав исходное изображение кнопки и выставив ее цвет на #C8FF00, мы придадим меню чуть более приличный вид. Теперь поменяем текст в элементе Text на PLAY PREJUMP и выставим шрифт 16-го кегля. Чтобы кнопка заработала, воспользуемся новой функцией, которую мы добавим в новый скрипт UIController для элемента Button. Скрипт состоит всего из одной функции, загружающей сцену уровня:
Эту функцию можно применить в опциях кнопки в панели Inspector. В настройках компонента Button (Script) можно сделать так, чтобы функция выполнялась, когда пользователь кликает данный компонент. С этой целью мы добавим новую функцию к событию On Click (), нажав иконку +. Теперь можно перетащить саму кнопку на поле ввода. Затем выберем только что написанную функцию из скрипта UIController (UIController.StartGame).
Публикуем проект как браузерную игру для WebGL
С Unity вы можете экспортировать ваш проект как приложение для WebGL. Откройте настройки сборки и выберите WebGL в качестве платформы. Затем подтвердите выбор, нажав кнопку Switch Platform. После этого остается только нажать кнопку Build и выбрать название для игры. По завершении сборки откройте html-файл с помощью любого браузера, поддерживающего WebGL.
Дальнейшие шаги
Конечно же, нашу небольшую игру можно улучшить. К примеру, добавить подсчет очков, разные типы платформ, дополнительные методы ввода, звуки и так далее. Главное, что мы увидели в данном уроке, это то, что кроссплатформенный игровой движок Unity предоставляет хорошее сочетание WYSIWYG-редактора и возможностей скриптов, создаваемых на похожем на JavaScript языке. В зависимости от конкретных требований проекта, Unity может стать достойной альтернативой WebGL-фреймворкам.
А вы используете Unity в ваших проектах? Есть ли у вас примеры интересных игр, написанных с помощью этого движка? Жду ответов в комментариях под записью.
WebGL на Unity3d — двенадцать проблем при сборке проекта
Не так давно в Unity вышла из беты возможность создания проектов для WebGL. Делюсь своим опытом сборки под эту платформу большого игрового проекта.
Disclaimer: Статья только для тех, кто сам собирается сделать что-то подобное — она очень техническая и использует специфическую для Unity терминологию.
1. Загрузка типа через reflection
Первая проблема: вот такой простой код будет работать неправильно:
Он работает, но тип возвращается неправильный и «пустой». Проблемы начинаются при работе с этим типом — почти все его методы возвращают пустые значения. Чтобы всё стало правильно, надо писать так:
Вот тут заявлено, что это — ожидаемое поведение.
2. Динамическая загрузка ресурсов
Следующая проблема возникает при использовании методов Resources.Load или Resources.LoadAll. Эти методы сказочно долго работают. Для однопоточного браузера это легко становится критичным. Динамическую загрузку ресурсов этими и схожими методами лучше не использовать вовсе. Где возможно, надо менять её на статическую (заранее проставлять ссылки на нужные префабы). Разница во времени загрузки на моем проекте доходила до десяти секунд.
3. Синхронизация файловой системы
Если вы пользуетесь файловой системой, то нужно знать, что она реализована как wrapper над базой данной, встроенной в браузер. Но самое главное — синхронизация браузера с этой базой происходит только по прямой яваскриптовой команде:
Если её не вызывать, то после выключения браузера данные могут и не сохраниться. Вызывать её можно так:
2) Вызываем это из c#:
4. Инициализация WebGL и компиляция шейдеров
При старте приложения существенное время может занимать инициализация WebGL. Это очень важно, потому что данное время учитывается браузером в общее время непрерывной работы скрипта, которое ограничено (в Firefox, например, оно 10 секунд по умолчанию).
Основное время при инициализации WebGL занимает компиляция шейдеров. Причем компилируются только те шейдеры, которые есть на сцене или в префабах, на которые есть со сцены ссылки. Если вы (как я) просто брали в свою игру много разных ассетов из разных источников, то шейдеров у вас будет неприлично много и их компиляция может занять более 10 секунд.
1) Минимизировать число различных шейдеров, используемых в проекте. Часто проект использует почти одинаковые шейдеры, которые приехали в него из разных покупных ассетов.
Для того, чтобы понять, какие шейдеры используются какими ассетами удобно написать скрипт.
5. Кэш Firefox
Если вы собираетесь отлаживать ваш проект, используя локальный сервер и Firefox, то столкнётесь с тем, что браузер неверно кэширует часть WebGL проекта.
Я делаю и запускаю версию 1. Она работает. Затем делаю версию 2. Запускаю — падает с непонятной ошибкой.
Лечится это ручной чисткой кэшей FF по адресу:
1) C:\Users\<ИМЯЮЗЕРА>\Application Data\Mozilla\Firefox\Profiles\<ИМЯПРОФИЛЯ>\storage\temporary\
Тут можно удалять вообще всё.
2) C:\Users\<ИМЯЮЗЕРА>\Application Data\Mozilla\Firefox\Profiles\<ИМЯПРОФИЛЯ>\storage\default\
Тут надо удалить только ваш сайт. Например
Замечание: Это происходит только при работе с HTTP сервером. Если запускать проект из файлов ошибки не происходит, поскольку кэши для каждого пути к файлам — разные. А вот для сервера один кэш, независимо от пути.
6. Script does not respond for a long time. Stop it?
Firefox спросит у вас об этом почти наверняка. Если проект большой, то может спросить и несколько раз.
Частично об этой проблеме говорилось выше — в пункте, посвященном инициализации WebGL. Но важно понимать как происходит загрузка WebGL проекта и какое время учитывает браузер, когда предлагает отстрелить зависший скрипт.
1) Идет загрузка данных браузером с сервера. Тут мы ничего ускорить не можем, но это время браузер и не ограничивает.
2) Идет декомпрессия данных.
Decompressed Release/w69.memgz in 100ms. You can remove this delay if you configure your web server to host files using gzip compression. UnityLoader.js:1:775
Decompressed Release/w69.jsgz in 391ms. You can remove this delay if you configure your web server to host files using gzip compression. UnityLoader.js:1:775
Decompressed Release/w69.datagz in 2764ms. You can remove this delay if you configure your web server to host files using gzip compression.
Как следует из пояснения, при правильной настройке сервера декомпрессия будет происходить налету и это время станет равным нулю. В любом случае, оно тоже не ограничивается.
Successfully compiled asm.js code (total compilation time 9088ms; stored in cache)1 56937f89-a8fd-4b65-94aa-453e33be78d8
Может занимать 5-10 сек, но тоже не ограничивается браузером, так как код игры еще не стартовал.
4) А вот дальше стартует код игры
И, с точки зрения браузера, код Unity (такой, как инициализация WebGL) не отличается от кода самой игры. Причем выполняются они одним куском, подряд. Поэтому тут за 10 секунд должна пройти и инициализация WebGL и пользовательский код. Учитывая, что время инициализации WebGL обычно не может быть сокращено сильнее, чем до 4-5 секунд на хорошем компьютере, тут лучше не рисковать и снизить время инициализации пользовательского кода до минимума. В идеале, он вообще ничего не должен делать. А как же тогда инициализировать игру? Её можно отложить. Например, так:
Смысл — сразу вернуть управление браузеру и через 0.1 секунду запускать свой код. После такого трюка браузер начнёт отсчёт 10 секунд заново. Соответсвенно, если инициализация у вас длинная, то можно и далее разбивать её на части таким же образом (хотя лучше постараться сократить — пользователь не любит ждать).
И последнее: ограничение на 10секунд непрерывного исполнения скрипта применяется не только во время инициализации.
7. Использование бандлов
При использовании asset bundles важно помнить, что они должны быть загружены с того же сервера, что и сама игра. Иначе работать не будет из-за нарушения single origin policy.
Второй момент — чтобы избежать задержки на декомпрессию бандла после загрузки лучше при создании бандла использовать не gz (по умолчанию), а lz4:
8. Ограничение на 512 mb
На моей машине ни один браузер не был в состоянии выделить игре более 512mb. Хотя памяти на машине много. Полагаю, что не нужно выделять играм, собираемым под WebGL, больше памяти, чем 512mb. И саму игру нужно делать так, чтобы ей этого хватало. В идеале вообще оставить 256mb которые стоят по умолчанию.
9. Strip engine code
Strip engine code — это галочка в настройках сборки, которая заставляет Unity выбрасывать из сборки неиспользуемые системные скрипты.
Это позволяет значительно сокращать итоговый объем сборки. Проблема здесь в том, что если какой-то код используется только ассетами, попавшими в бандл, он тоже будет выброшен. А итоговая сборка работать не будет. Причем exception будет абсолютно непонятный.
1) Если получили непонятный exception, попробуйте собрать убрав эту галочку;
2) Можно прямо использовать скрипты, нужные ассетам из бандлов в коде или использовать специальую возможность Unity — файл link.xml.
10. Developer builds — no fast builds!
Когда собираешь developer build есть возможность выбора между быстрой сборкой и быстрым исполнением. На самом деле быстрая сборка занимает почти такое же (громадное) количество времени и при этом, на большом проекте часто совсем не работает из-за нехватки памяти. Лучше её не использовать.
11. Crunched textures
Для проектов под WebGL всегда используйте crunched texture. Иначе объем игры будет неприлично большим для web приложения.
Чтобы не выставлять тип каждой текстуре вручную, можно использовать вот такую технику (нужно перегрузить метод OnPostprocessTexture).
А двенадцатой пробемы нет, просто «двенадцать проблем» звучит лучше, чем одиннадцать.
UPDATE от 1го июня 2017г:
Двенадцатая проблема все-таки есть. Связана она с передачей строковых параметров между Unity и WebGL плагинами. Передача строки из Unity в webgl работает нормально. А вот наоборот — не всегда.
Unity предлагает использовать такой вот snippet:
var buffer = _malloc(val.length + 1);
writeStringToMemory(val, buffer);
Здесь — val — строка в javascript.
Это работает до тех пор, пока длина строки совпадает с количеством байтов в ней. Как только в строке встречаются неоднобайтовые (например, русские) символы, этот код приводит к записи за пределы выделенной памяти, что выражается в случайных крэшах вашего приложения в произвольных местах.
Так что либо используйте encode, чтобы ваши строки использовали лишь символы из latin1, либо перепишите этот snippet так, чтобы он использовал количество байт, а не символов в строке.
Getting started with WebGL development
This section provides an overview of the platform support and other useful technical information required to build your project using the WebGL option.
Technical Overview
Unity uses the emscripten The toolchain that Unity uses to convert from C and C++ to WebAssembly. More info
See in Glossary compiler toolchain to cross-compile the Unity runtime code (written in C and C++) into WebAssembly (also known as Wasm). It is designed to be small in size, load-time and memory efficient as well as able to deliver close to native speed execution. For more information about WebAssembly in Unity, check this blog post.
Platform support
Unity WebGL content is supported in the current versions of most major browsers on the desktop, however there are differences in the level of support offered by the different browsers. Mobile devices are not supported by Unity WebGL.
Not all features of Unity are available in WebGL builds, mostly due to constraints of the platform. Specifically:
Threads are not supported due to the lack of threading supporting in JavaScript. This applies to both Unity’s internal use of threads to speed up performance, and to the use of threads in script code and managed dlls. Essentially, anything in the System.Threading namespace is not supported.
WebGL builds cannot be debugged in Visual Studio. See: Debugging and trouble shooting WebGL builds.
The WebGL graphics API is equivalent to OpenGL ES 2.0 and 3.0, which has some limitations. See: WebGL Graphics.
WebGL builds use a custom backend for Audio, based on the Web Audio API. This supports only basic audio functionality. See: Using Audio in WebGL.
Getting Started with WebGL Development
What is Unity WebGL?
The WebGL build option allows Unity to publish content as JavaScript programs which use HTML5 technologies and the WebGL rendering API to run Unity content in a web browser. To build and test your content for WebGL, just choose the WebGL build target in the Build Player window, and hit Build & Run.
Technical overview
To run in WebGL, all our code needs to be JavaScript. We use the emscripten compiler toolchain to cross-compile the Unity runtime code (written in C and C++) into asm.js JavaScript. asm.js is a very optimizable subset of JavaScript which allows JavaScript engines to AOT-compile asm.js code into very performant native code.
Platform support
Unity WebGL content is supported in the current versions of most major browsers on the desktop, however there are differences in the level of support offered by the different browsers. Mobile devices are currently not supported by Unity WebGL.
Not all features of Unity are available in WebGL builds, mostly due to constraints of the platform. Specifically:
Threads are not yet supported due to lack of threading supporting in JavaScript. This applies to both Unity’s internal use of threads to speed up performance, and to the use of threads in script code and managed dlls (Basically, anything in the System.Threading namespace is not supported).
WebGL builds cannot be debugged in MonoDevelop or Visual Studio. See: Debugging and trouble shooting WebGL builds.
Browsers will not allow direct access to IP sockets for networking due to security concerns. See: WebGL Networking.
The WebGL graphics API is equivalent to OpenGL ES 2.0, which has some limitations. See: WebGL Graphics.
WebGL builds use a custom backend for Audio, based on the Web Audio API. This supports only basic audio functionality. See: Using Audio in WebGL.