Search Unity

New Unity WebGL Embedding API White Paper

Discussion in 'Web' started by alexsuvorov, Sep 13, 2016.

  1. alexsuvorov

    alexsuvorov

    Unity Technologies

    Joined:
    Nov 15, 2015
    Posts:
    327
    Currently we are working on a new embedding scheme for WebGL which should make embedding more convenient, provide namespacing for all the WebGL code in order to avoid interference with the rest of the page, and allow simultaneous embedding of multiple WebGL content on the same page without using iframes.


    Instantiation

    WebGL content instantiation function is provided with a DOM element (a container for the WebGL content, which can be a usual div and may serve as a placeholder prior to the content instantiation, the element can also be created dynamically), or a DOM element id (this should work even if the element with the provided id has not been parsed yet, i.e. when instantiation is performed in the html header).

    After you include UnityLoader.js in your html:
    Code (html):
    1. <script src="UnityLoaderFolder/UnityLoader.js"></script>
    you can use the UnityLoader object to instantiate your game, using for example:
    Code (JavaScript):
    1. var myGame = UnityLoader.instantiate(gameContainer, "http://mydomain/myfolder/Build/mygame.json");
    or
    Code (JavaScript):
    1. var myGame = UnityLoader.instantiate(gameContainer, "http://mydomain/myfolder/Build/mygame.json", {width: 800, height: 600});
    or
    Code (JavaScript):
    1. var myGame = UnityLoader.instantiate("gameContainerId", "http://mydomain/myfolder/Build/mygame.json", {onProgress: myProgress, Module: {TOTAL_MEMORY: 0x20000000}});
    UnityLoader.js will not depend on a specific build and generally should be the same for any WebGL build (means it can be shared on a public domain). UnityLoader already contains the default Unity logo and a progress bar, though it can be overridden via additional instantiation parameters. Additional parameters can be also used to override most of the module variables and handlers.

    mygame.json will contain all the necessary information to instantiate the build (including parameters and links that have been previously specified in the html). The default module parameters can be overridden from the .json, and the .json parameters can be overridden from the embedding html.

    The minimal setup should look something like this:
    Code (html):
    1. <html>
    2.   <head>
    3.     <script src="UnityLoader.js"></script>
    4.     <script>
    5.       var myGame = UnityLoader.instantiate("gameContainerId", "http://mydomain/myfolder/Build/mygame.json");
    6.     </script>
    7.   </head>
    8.   <body>
    9.     <div id="gameContainerId" style="width: 960px; height: 600px; margin: auto"></div>
    10.   </body>
    11. </html>
    All the internal game variables will be wrapped inside the loader functions and therefore will not interfere with the rest of the page or other embedded modules.

    The loader will provide the overall build download progress value to the onProgress callback (while the current loader only monitors the download of the .data file). The server should provide Content-Length headers in order for the loading progress to be calculated precisely, otherwise the loading progress will be approximated.


    Interaction

    Interaction with the instantiated game module is performed through the UnityLoader API, for example:
    Code (JavaScript):
    1. myGame.SetFullscreen(1);
    or
    Code (JavaScript):
    1. myGame.SendMessage("myObject", "myFunction", "foobar");


    Distribution

    All the WebGL build files (except the UnityLoader.js and .json) will have the same file extension, which should simplify the server setup (i.e. when specifying MIME types for IIS). Compressed and uncompressed content will have the same file extension (no more suffixes required) and will be distributed in the same way, the loader will autodetect the used compression method and automatically perform decompression in JavaScript when necessary. The developer will still be able to provide appropriate Content-Encoding headers in order to speed up decompression of the content. File links will be relative to the .json file location, which should simplify embedding of the WebGL content from another document location or even from another domain (given that CORS headers are provided).


    Hash based distribution (optional)

    We are also considering a distribution scheme where file names are generated based on the content hashsum.

    Consider the following example of the Build folder on the server:

    /Build/2e06b6c4670ab40d15e1dcb62436c9b2.json
    /Build/c31a82de01db52817dd87d4f1858ee67.json
    /Build/57b5a31d2566cf227c47819eb3e5acfa.unityweb
    /Build/88a7f58a1038e96586d84b27c3354d5c.unityweb
    /Build/8c9889fd3f9272b942d4868a9c1b094c.unityweb
    /Build/accdec8ac01fc3e501da10f0fd8cfaec.unityweb
    /Build/c05b5450212180ed3d9960c1a050f87d.unityweb


    where /Build/2e06b6c4670ab40d15e1dcb62436c9b2.json is:
    Code (json):
    1. {
    2.   "TOTAL_MEMORY": 268435456,
    3.   "dataUrl": "88a7f58a1038e96586d84b27c3354d5c.unityweb",
    4.   "codeUrl": "57b5a31d2566cf227c47819eb3e5acfa.unityweb",
    5.   "asmUrl": "8c9889fd3f9272b942d4868a9c1b094c.unityweb"
    6. }
    In this case, after building a new version of the game, the developer can just copy the new build directly into the same folder on the server, skipping copy of the duplicate filenames. If some files have not changed between builds (for example the updated build has some assets changed but the code remains the same), then all those unchanged build files will remain unchanged on the server and will have the same url, which means they can be served directly from the browser cache, given that the user has already played the previous version of the game. All the previous builds can be simultaneously available from the same server folder, while you will only have to provide the appropriate .json link.

    Another reason to use hash based filenames is that this way you can guarantee (up to the degree of the hash collision probability) that urls will not be reused for different content. This is especially important for CDNs and other cached environments, as it guarantees that there will be no version mismatch between the different files in the build (which could happen before if the content from different reused urls got different lifetime in the intermediate caches). This also means that you will be able to set appropriate Cache-Control header for this content to explicitly prevent its revalidation, which should speed up the loading of the cached content.

    In addition, we are currently also working on a new caching mechanism for Unity WebGL, which will cache the downloaded content in the indexedDB along with the server response headers. This caching mechanism will be able to emulate the browser cache functionality without having its limitations for the cached content lifetime and size (the Data caching build option will then become deprecated, as you will be able to cache all the build files and not just data, while the versioning will be based on the Last-Modified and/or ETag headers provided by the server). Having hashed filenames will allow developers to avoid content revalidation (particularly important when using a lot of small asset bundles) and cache the content more reliably, even without having any access to the server response headers (i.e. when using public drives and storages).


    Feel free to provide any feedback and share additional ideas regarding the future WebGL embedding API.
     
    Last edited: Sep 13, 2016
    Novack, alfish, KristianDoyle and 4 others like this.
  2. theylovegames

    theylovegames

    Joined:
    Aug 18, 2012
    Posts:
    176
    Looking forward to having Microphone support in WebGL. In the meantime I'll need to create an HTML5 microphone and interact with it using SendMessage. I need access to the wave data so I suspect I'll be passing a lot of data across the data bridge. Is there a way to have a shared array or something? Using SendMessage will be slow when passing 44100 sample rate worth of data.
     
  3. alexsuvorov

    alexsuvorov

    Unity Technologies

    Joined:
    Nov 15, 2015
    Posts:
    327
    Hello theylovegames.

    Please note that this thread is specific to the WebGL embedding API, which is mostly related to the build distribution, downloading, caching and instantiation. So you might want to create a separate thread specific to the microphone support. Answering your question, yes, you may try the following solution which should let you share a typed array between JavaScript and managed code: http://forum.unity3d.com/threads/tr...webgl-browser-javascript.394861/#post-2576106
     
  4. Deleted User

    Deleted User

    Guest

    Sounds like awesome changes! Especially interested in the hash based distribution and new caching mechanism. Do you have a timeline when those would be available?
     
  5. alexsuvorov

    alexsuvorov

    Unity Technologies

    Joined:
    Nov 15, 2015
    Posts:
    327
    Hello JyriKilpelainen.

    There are no specific release dates at the moment, however most of this functionality has already been implemented. There still are some open questions which might affect the future design, for example, a question about the IndexedDB cache cleanup. At some point you will have to clean up the cached data in order not to run out of storage space. Currently we are considering two main approaches:

    a) Application can explicitly set the maximum indexedDB cache size, in which case the least recently used files will be discarded from the indexedDB cache in case of overflow.

    b) At launch time the application can provide a list of all the urls it might potentially use (including asset bundles, streaming assets etc.), in which case all the urls outside of this list can be removed from the indexedDB cache. This approach is more sophisticated as there might be multiple WebGL applications hosted on the same domain, which would then require separate caches in order to not clean up each other. Alternatively, the url whitelist can be treated as folder specific (and will cause cache cleanup only for urls under a specific subfolder).

    There might also be some other approaches to this which should be considered.
     
    Last edited: Sep 14, 2016
  6. Foriero

    Foriero

    Joined:
    Jan 24, 2012
    Posts:
    584
    Hi Alex, this will be included in 5.5f1? I made some custom WordPress plugin and if possible I would love to know more.
     
  7. ojt

    ojt

    Joined:
    Nov 7, 2008
    Posts:
    2
    Hi,

    This is very good news. Quite excited to get my hands on the new API :)

    On that note, is there more recent documentation / other information available or is this all for now? I know this was mentioned at the Unite in LA so I was just wondering if there was further information somewhere.

    Thanks, and looking forwards to testing this.
     
  8. Schubkraft

    Schubkraft

    Unity Technologies

    Joined:
    Dec 3, 2012
    Posts:
    1,071
    This will not be in 5.5, sorry. We are aiming for 5.6 with this which will enter beta in a few weeks.
     
  9. alexsuvorov

    alexsuvorov

    Unity Technologies

    Joined:
    Nov 15, 2015
    Posts:
    327
    Hello Foriero.

    The new API is targeting Unity 5.6.
    Currently the recommended way to embed WebGL content on WordPress and similar websites is to use iframe. This way JavaScript environments get naturally isolated and build content paths are relative to the embedded index.html file. Moreover, iframe solution should also work fine with future builds using the new API.

    Contrary to the popular belief, there is nothing wrong with embedding your game in an iframe (as has been already mentioned in other threads). However, in some cases you might want to have higher integration with the embedding page. For example:
    • The embedded WebGL content is hosted on another domain.
      When embedding cross-origin content in an iframe, you have to use postMessage for communication, which brings some complications (i.e. the call is asynchronous, you can not handle exceptions etc.). While embedding without iframes lets you call the parent window functions from the game directly (and vice versa).

    • You have multiple WebGL games which can be run from the same embedding page on user click.
      In this scenario you would normally create a new iframe each time user selects a new game, and destroy this iframe afterwards. This will cause reallocation of the heap each time the user selects a new game, so the browser memory becomes fragmented, which will eventually cause out of memory after a few iterations in a single process 32-bit browser. When embedding content without iframes, you will be able to allocate the heap just once and then reuse it for each selected game without reallocation (considering that this shared heap is large enough).

    • You have multiple WebGL games on the same page, which use different assets, but share the same code (for example, user-created games based on the same build, model viewers etc.).
      When using iframes, you should be able to load the next selected WebGL content in the same iframe without relaunching and recompiling the build, by simply reloading the build data. However, you will not be able to move this iframe within the DOM tree without reloading it (consider the case when user clicks on a thumbnail in a list for 3d-preview). When embedding without iframes, you can freely move the embedded node within the DOM tree without reloading it.
     
    Last edited: Nov 7, 2016
  10. Foriero

    Foriero

    Joined:
    Jan 24, 2012
    Posts:
    584
    Hi Alex,

    Yes I have solution NOT using iframe. It works great and is so called "responsive". So no matter on which device it always nicely fits into wordpress presentation. The only what is missing is to have possibility to have more webgl apps on one page. Which as I understand will be possible with your new export and a minor change in our wordpress plugin. Once I have it I will push it to asset store so that other developers can use it as well.

    Thank you very much and look forward to 5.6

    Have a great day, Marek.
     
  11. tschera

    tschera

    Joined:
    Nov 22, 2016
    Posts:
    10
    @alexsuvorov this is a very interesting approach and I think there are many improvments to the current embedding mechanism.
    One thing which bothers me is the *.unityweb file extension. Will this be some proprietary file format? Why not stick to regular JavaScript?
     
  12. alexsuvorov

    alexsuvorov

    Unity Technologies

    Joined:
    Nov 15, 2015
    Posts:
    327
    Hello tschera.

    Quick answer:
    In case of uncompressed build, it will be a "regular JavaScript" file, just with a different file extension.


    Detailed answer about the .unityweb extension in general:

    1) In the current version of Unity we use at least 4 different extensions: .js, .data, .mem, .unity3d. And if you are using build compression, you will get 4 more extensions: .jsgz, .datagz, .memgz, .unity3dgz. This brings complications for IIS server setup, as by default IIS does not serve files with undefined MIME types. The developer should therefore add all those new extensions (except the .js) to the server configuration, which is a bit inconvenient and might also conflict with already existing configuration (especially for the .jsgz extension).

    It would therefore make more sense to have only one extension for at least .data, .mem, .unity3d, .jsgz, .datagz, .memgz, .unity3dgz files, so that developer can setup MIME type only for this single extension just once and globally for the whole server (it is very unlikely that .unityweb extension will conflict with any other application running on the server).

    2) Normally, you would generate a compressed JavaScript file rather than regular JavaScript, as generating an uncompressed build only makes sense if your server supports static compression with the desired compression method. And even in that case there are some drawbacks:
    - Intermediate proxy or anti-virus software might strip out the initial Accept-Encoding header, in which case you will end up with fully uncompressed build being served to the user (however this situation is quite rare).
    - Files statically compressed with brotli can only be served over https, while compressed build gives you an option to also serve it over http and decompress in JavaScript.

    In case of uncompressed build, .unityweb extension of the JavaScript files might bring some inconvenience when opening the file in the editor (no syntax highlighting, no file association). However, using .js extension for those files might bring issues when downloading the file from the server (if server decides to use chunked encoding, there will be no Content-Length header provided, so the download progress will be displayed incorrectly).

    3) Why using the same extension for compressed and uncompressed files?
    Currently used setup gives you ability to shift between different approaches using just server configuration (if you have sufficient access right to the server configuration, you may optionally add Content-Encoding: gzip header to the served content and rewrite the request url, in which case it will be decompressed by the browser). Although this approach is universal, it has some disadvantages:
    - It brings significant complications for developers not familiar with server setup when their server configuration is not default (affects both Apache and IIS).
    - In cases when Content-Encoding header is not set, the loader wastes some time to perform the initial request to a non-existing file before it switches to the decompression fallback.

    It would make more sense to perform only one request to a file that necessarily exists, without having to use any redirection or header manipulations on the server side. The loader will automatically detect the compression method of the downloaded file and decompress it if necessary. In practice it means the following. Your JavaScript file can be uncompressed, compressed with gzip, or compressed with brotli, depending on the compression method selected at build time. In all those cases the file will have exactly the same .unityweb extension, the loader will take care of the rest.

    Note that you can still speed up the decompression on the client side if you append appropriate Content-Encoding header, but you no longer have to rely on the mod_rewrite or URL Rewrite functionality, which significantly simplifies the server setup. If you append the appropriate Content-Encoding header, then the loader will receive a file already decompressed by the browser. If you do not append Content-Encoding header, then the loader will receive a compressed file and will automatically decompress it. In other words, whether the file will be received compressed or uncompressed is determined by the server configuration and is not known at build time, therefore a universal extension is used.
     
    Last edited: Nov 30, 2016
  13. tschera

    tschera

    Joined:
    Nov 22, 2016
    Posts:
    10
    @alexsuvorov thanks for the detailed explanation. So the main reason is easier server setup. But then adding the js build with a script tag won't be possible anymore. This means we are "forced" to parse and interprete the .unityweb file on the client in JavaScript. I was playing around with adding the JS file via script tag after I read the following thread and we also felt a performance gain (especially in Chrome). But when I understand it right, we could just rename the .unityweb file to .js and still use it as regular js file.
    This would give us again the freedom of handing the build as regular js file to the browser and the browser could parse and interpret the js file with the browser engine (and use all the internal tricks for compiling, parsing, interperting etc)
     
  14. alexsuvorov

    alexsuvorov

    Unity Technologies

    Joined:
    Nov 15, 2015
    Posts:
    327
    Yes, if you are creating an uncompressed build. And by the way, note that remote file does not need to have .js extension in order to be loaded via script tag, i.e. the following code should work:
    Code (html):
    1. <script src="anyfile.anyextension"></script>
    2. <script src="myscript.php?version=1"></script>
    I am just not completely sure if proper Content-Type response header is required for streamed parsing in any browser or not, you can test it if you wish.

    The script tag is not used by default for the following reasons:
    - You will not be able to monitor the code download progress, which is especially important for builds with large code and small data.
    - It makes namespacing more complicated. You should either store meta information about the code externally, or the code should send information about itself on load (both solutions are not very reliable)
    - You only gain about 1 second in Chrome, and only if your data file downloads faster than the code.
    - With the new setup it will be much easier for the developer to customize the loader for his specific needs, as the loader will be independent from the build (you can adjust it just once and it will work for all your subsequent game updates compiled with the same version of Unity).
     
    Last edited: Dec 1, 2016
  15. tschera

    tschera

    Joined:
    Nov 22, 2016
    Posts:
    10
    Cool, I'll try that. Setting up the server to send the right Content-Type is not a problem for us.

    I agree with your points almost. But with the script embedding I don't agree totally with you. I think 1 second is a huge performance gain. I mean it's 10% improvment if startup time is about 10 seconds (like our startup time is). The thing with the data file is different in my opinion because one can work around this issues by laziliey fetching data and resources, but the problem of parsing-time can not be improved by us developers. Another example of how "much" 1 second is, are the violations shown by Chrome Canary. They show violations if a procedure in a requestAnimationFrame takes more than 60ms. So I think 1000ms and 10% performance gain is significantly. And I think many small improvements also add up to great performance gains. But maybe our usecase is different, because for us startup time is very important
     
  16. alexsuvorov

    alexsuvorov

    Unity Technologies

    Joined:
    Nov 15, 2015
    Posts:
    327
    Hello tschera.

    This is a reasonable concern. I believe we can provide an "undocumented" way to achieve what you want using minimal effort. All you would have to do is to add a small prefix to your uncompressed JavaScript file on the server (something like UnityLoader["myUniqueId"]=...), and add an additional parameter to the UnityLoader.instantiate() function at the embedding page (something like {asmId: "myUniqueId"}). Then the loader could "load and parse" the asm.js module via dynamically created script tag.

    In this case you should take care of the file id being unique (this id is required for code namespacing and would otherwise be generated automatically). Also, the loading progress of the asm.js file can not be determined in this case. Means if the data loads faster than the code, you will see 100% complete bar for a while, until the code is fully loaded. And if the data loads slower than the code (for at least a second), then using a script tag wont bring you any advantage.
     
    Last edited: Dec 3, 2016
  17. tschera

    tschera

    Joined:
    Nov 22, 2016
    Posts:
    10
    @alexsuvorov thanks for the explanation. It sounds reasonable :)

    "using minimal effort" is not the most important thing for us. For us it is important that there is an "official" solution which does not break on every update (and which can be automated and integrated into our build pipline).
     
  18. stephanwinterberger

    stephanwinterberger

    Joined:
    Aug 22, 2016
    Posts:
    27
    I don't think that 1 second is nothing and negligibly. Especially on the web users are used to fast websites. Otherwise they just abandon the website. In 2015 Google presented a statistic which showed that 1 second leads to 11% less page views and 16% decrease in customer satisfaction. For more details see from minute 4:


    I think Unity still does not load (and start) fast on the web platform, therefore we should squeeze out every single bit of performance possible. In my point of view, wasting 1 second just for convenience is not the best way to go.
     
    StaffanEk likes this.
  19. alexsuvorov

    alexsuvorov

    Unity Technologies

    Joined:
    Nov 15, 2015
    Posts:
    327
    Hello stephanwinterberger.

    As has been mentioned above, it is not possible to win this second without any drawbacks. Moreover, you will still be able to load the asm.js module via a script tag if you think that in your specific situation the advantages overweight the drawbacks. Assuming that you have huge experience in server setup and your server does not have any limitations, you will still face the following problems:

    - It is not possible to display the loading progress of a script loaded via tag.
    First, you can only get some advantage from using a script tag if your compressed data size is smaller than your compressed code size. So let's consider that your data size is twice smaller than your code size. In default setup the user will see a loading bar evenly filled up for 10 seconds and then stuck at 100% for 1 second. When using a script tag the user will see a loading bar evenly filled up for 5 seconds and then stuck at 100% for 5 seconds. Would be interesting to have updated statistics of users abandoning the page considering this aspect. (Note: the numbers have been chosen approximately just to demonstrate the idea, i.e. it is not taken into account that the code download speed might increase after the data download is finished etc.)

    - Unlike the default setup, the code loaded via a script tag can not be reliably cached in the indexedDB.
    This means that on subsequent launches there is always some risk of the script being removed from the browser cache, as well as the risk of it not being cached at all due to size limitations (mostly applies to Safari). On the other hand, in default setup the script will be cached in the indexedDB, which is much more reliable than the browser cache. (Note: technically, with some overhead, you can get the script body and put it into the database even if it was loaded via a script tag, but what you actually can not do in this case is to revalidate the cached script, i.e. you will not be able to reliably track the changes of the script file on the server side)

    You are right, there are some situations when developer can improve the loading time by modifying the default setup. The real question here is whether that specific modification should become the default. For example, @tschera suggested to make this modification an officially supported build option, which is quite reasonable. In other words, this should rather qualify as a "feature", that might get official support after its efficiency is proven on real examples in updated loader setup (while time comparison mentioned above is based on versions 5.3 and 5.4).
     
    Last edited: Dec 10, 2016
  20. stephanwinterberger

    stephanwinterberger

    Joined:
    Aug 22, 2016
    Posts:
    27
    Thats true that the size is a problem, not only for caching. But that's another story ;)
    This sounds quite good. We don't have problems with the constraints of the script-tag variant so for us this would help.

    But with my previous comment, I wanted to stress that also one second matters a lot. And that it matters especially in the web. I think it's dangerous to say: "It's just a second". If you say this 10 times then it's 10 seconds ;) so also seconds add up and every additional second will distract more users.

    I know that you can not compare Unity with an e-commerce website but users are used to fast websites so we should try to provide a great perceived performance for the users. But if we spend 10 seconds on parsing some JavaScript, it is almost impossible to improve perceived performance. Hopefully there will be improvements coming! Looking forward to it ;)
     
  21. stephanwinterberger

    stephanwinterberger

    Joined:
    Aug 22, 2016
    Posts:
    27
    I wanted to add one more thing, if we use a script tag and add the async attribute, the script is parsed in a separate thread. Which would minimize UI stall, a very important (and annoying) issue for us. And of course parsing would be faster.

    parsing-thread.jpg
    Source: https://twitter.com/addyosmani/status/808713528160813056
     
  22. alexsuvorov

    alexsuvorov

    Unity Technologies

    Joined:
    Nov 15, 2015
    Posts:
    327
    Hello stephanwinterberger.

    The attached chart is a bit disproportional in regards to WebGL build, as the script parsing time is normally significantly smaller than the loading time. It would be much more interesting if you could test those assumptions on a real build and share the results. You are able to use async script tag for an already existing build using the following guide: https://forum.unity3d.com/threads/f...load-the-build-as-a-blob.435906/#post-2822014

    In order to check if the main thread has been stuck you can for example attach the following script to the beginning of your html (adjust the parameters if necessary):
    Code (html):
    1. <script>
    2.   setInterval(function delayTest() {
    3.     if (Module && Module.calledRun)
    4.       return;
    5.     var time = Date.now();
    6.     if (delayTest.time && delayTest.time + 100 < time)
    7.       console.log("the main thread has been blocked for " + (time - delayTest.time) + " ms");
    8.     delayTest.time = time;
    9.   }, 1);
    10. </script>

    Here are the test results for an uncached AngryBots build when loading the script via blob (default behavior) in Chrome, Firefox and Safari:
    notag.png

    Here are the test results for an uncached AngryBots build when loading the script via async script tag in Chrome, Firefox and Safari:
    tag.png

    As you can see, using a script tag does reduce UI block time in Chrome for over a second, makes no difference in Firefox, and actually increases UI block time in Safari for over a half of a second (note that increased main thread block time does not necessarily mean that the build starts slower). So although the problem is being presented as a "browser problem", it is only reproducible in Chrome, while in Safari you get the opposite results.

    Feel free to double check it yourself, as for your specific build the results might be different. Though I would suggest to continue the discussion about the async script tag in the following thread: https://forum.unity3d.com/threads/f...-unityloader-load-the-build-as-a-blob.435906/
     
    Last edited: Dec 14, 2016
  23. stephanwinterberger

    stephanwinterberger

    Joined:
    Aug 22, 2016
    Posts:
    27
    @alexsuvorov: thanks for the detailed explanation on how to measure UI stall. I like your idea with the setInterval I think it's a clever way to measure the UI stall. I'll make measurements on our project and let you know in the other thread (https://forum.unity3d.com/threads/f...-unityloader-load-the-build-as-a-blob.435906/).

    But actually I didn't want to go into real numbers. I wanted to emphasize that if we rely on regular (often used) browser features we will get improvements for free. I'm not sure if loading 25mb of JavaScript as Blob is a use case which browser vendors address. But I'm not in contact with browser vendors so maybe I'm wrong ;) On the other hand I see browsers improving on many well known pain points (at a fast pace). This is not exclusively true to "how we load" a Unity build. And therefore I think standard browser features should be considered in all aspects of the WebGL platform. The more we can use of them the more we are leveraging from the web platform.
     
  24. Foriero

    Foriero

    Joined:
    Jan 24, 2012
    Posts:
    584
    Is there any documentation about new embedding in 5.6?
     
  25. Foriero

    Foriero

    Joined:
    Jan 24, 2012
    Posts:
    584
    Can anyone a bit elaborate how this works?

    Code (csharp):
    1.  
    2. function UnityProgress(gameObject, progress) {
    3.   if (!gameObject.Module)
    4.     return;
    5.   if (!gameObject.logo) {
    6.     gameObject.logo = document.createElement("div");
    7.     gameObject.logo.className = "logo " + gameObject.Module.splashScreenStyle;
    8.     gameObject.container.appendChild(gameObject.logo);
    9.   }
    10.   if (!gameObject.progress) {    
    11.     gameObject.progress = document.createElement("div");
    12.     gameObject.progress.className = "progress " + gameObject.Module.splashScreenStyle;
    13.     gameObject.progress.empty = document.createElement("div");
    14.     gameObject.progress.empty.className = "empty";
    15.     gameObject.progress.appendChild(gameObject.progress.empty);
    16.     gameObject.progress.full = document.createElement("div");
    17.     gameObject.progress.full.className = "full";
    18.     gameObject.progress.appendChild(gameObject.progress.full);
    19.     gameObject.container.appendChild(gameObject.progress);
    20.   }
    21.   gameObject.progress.full.style.width = (100 * progress) + "%";
    22.   gameObject.progress.empty.style.width = (100 * (1 - progress)) + "%";
    23.   if (progress == 1)
    24.     gameObject.logo.style.display = gameObject.progress.style.display = "none";
    25. }
    26.  
     
  26. Foriero

    Foriero

    Joined:
    Jan 24, 2012
    Posts:
    584
    Are there any other events we can hook up ( UnityLoader.js )? Especially I'm interested when progress == 1 it takes like 3 sec before unity really lunches. So we would love to have event like OnGameRun or similar so that we can hide our loading styles. If we hide them when progress == 1 as I said we have 3sec black screen.
     
  27. Foriero

    Foriero

    Joined:
    Jan 24, 2012
    Posts:
    584
    Also how can we achieve that the instantiated UnityPlayer keep same aspect ratio of the placehoder div? ( when that placeholder div is stretched width:100%;margin-top:52% ( which is 16:9 ))
     
  28. alexsuvorov

    alexsuvorov

    Unity Technologies

    Joined:
    Nov 15, 2015
    Posts:
    327
    Hello Foriero.

    In Unity 5.6, html template is completely separate from the build and therefore can be easily customized. When you instantiate a build, you can provide a download progress callback (onProgress) to the UnityLoader.instantiate function in the following way:
    Code (JavaScript):
    1. var gameInstance = UnityLoader.instantiate("gameContainer", "Build/your-build.json", {onProgress: UnityProgress});
    In which case the provided UnityProgress function will be called each time when the total build download progress updates. It is called with two arguments: gameObject and progress. The gameObject argument identifies the gameInstance which download progress has been updated (actually, the name of this parameter in the template is confusing and should be replaced with gameInstance in the future). This gameInstance argument lets you use the same progress callback function for different game instances. The progress argument is a numerical value in the range from 0 to 1, which specifies the current download progress for the instantiated build. The callback function from the default template is just an example, you are free to modify it in any way you like.
    Let's consider this example. As has been explained before, the onProgress callback is called when the download progress gets updated. However, it does not accurately represent the total startup progress. For example, after your asm.js module has been downloaded, it should also be parsed and compiled, which might take a few seconds (not that all the download operations are performed in parallel, so this compilation delay should only be noticeable if your code size is bigger than your data size). Unfortunately, browser does not provide any way to monitor asm.js compilation progress, therefore it is not reflected in the progress bar.

    But what you can do to avoid the black screen confusion, is to hide the progress bar a bit later, just before the build startup. The right place to do it would be in the onRuntimeInitialized callback. You can override it by adding a Module property to the UnityLoader.instantiate additional argument (note that this way you can also override other Module parameters, like TOTAL_MEMORY etc. directly from the index.html):
    Code (JavaScript):
    1. var gameInstance = UnityLoader.instantiate("gameContainer", "Build/your-build.json", {onProgress: UnityProgress, Module: {
    2.   onRuntimeInitialized: function () {
    3.     UnityProgress(gameInstance, "complete");
    4.     // do something else
    5.   },
    6. }});
    Now your UnityProgress function will be called with the "complete" value of the progress argument when the build is ready to launch. So let's use this as a trigger to hide the progress bar in the UnityProgress.js (instead of the progress == 1):
    Code (JavaScript):
    1. function UnityProgress(gameObject, progress) {
    2.   if (!gameObject.Module)
    3.     return;
    4.   if (!gameObject.logo) {
    5.     gameObject.logo = document.createElement("div");
    6.     gameObject.logo.className = "logo " + gameObject.Module.splashScreenStyle;
    7.     gameObject.container.appendChild(gameObject.logo);
    8.   }
    9.   if (!gameObject.progress) {  
    10.     gameObject.progress = document.createElement("div");
    11.     gameObject.progress.className = "progress " + gameObject.Module.splashScreenStyle;
    12.     gameObject.progress.empty = document.createElement("div");
    13.     gameObject.progress.empty.className = "empty";
    14.     gameObject.progress.appendChild(gameObject.progress.empty);
    15.     gameObject.progress.full = document.createElement("div");
    16.     gameObject.progress.full.className = "full";
    17.     gameObject.progress.appendChild(gameObject.progress.full);
    18.     gameObject.container.appendChild(gameObject.progress);
    19.   }
    20.   if (progress == "complete") {
    21.     gameObject.logo.style.display = gameObject.progress.style.display = "none";
    22.     return;
    23.   }
    24.   gameObject.progress.full.style.width = (100 * progress) + "%";
    25.   gameObject.progress.empty.style.width = (100 * (1 - progress)) + "%";
    26. }
    So the basic limitation here is that some processes involved in the build launch do not provide progress information, so the total startup progress can not be displayed to the user precisely. Some users might possibly think that something went wrong after they see 100% for a few seconds. So you may consider some tricks to reduce confusion for the users, like for example always display not more than 99% until the build is actually ready to launch (even though it has been 100% downloaded).
     
  29. Foriero

    Foriero

    Joined:
    Jan 24, 2012
    Posts:
    584
    Hi Alex,

    Thank you for the reply. OnRuntimeInitialized works great. We have completely smooth loading experience. I'm very happy.

    Last thing we try to solve is how to tell player to keep the width:100%;padding-top:52% that we define in wordpress?

    Code (csharp):
    1.  
    2. "<div id=\"".$gameId."\" class=\"gamecanvas\" style=\"padding-top:".$aspectRatio."%;background-color=".$backgroundColor."\">".
    3.                 "</div>".  
    4.  
    So basically this is the target div that is stretched over the wordpress column ( wherever that column is ). So we need to force UnityLoader.instantiate to follow this style so that resulted div after instantiate using it. Any idea how to do it? Should I in code after I instantiate write this style with javascript directly into that resulted div from UnityLoader?

    Thank you very much, Marek.
     
  30. Foriero

    Foriero

    Joined:
    Jan 24, 2012
    Posts:
    584
    Just to add. After we call UnityLoader.instantiate our div is overwritten by this. Which is completely what we dont want. :) May be pass inside instantiate style we want to use?

    Code (csharp):
    1.  
    2. <div id="a41e5131d8" class="gamecanvas" style="padding: 0px; margin: 0px; border: 0px; background: rgb(34, 44, 54);">
    3. <canvas id="#canvas" width="1160" height="580">
    4. </canvas>
    5. </div>
    6.  
     
    konsnos likes this.
  31. alexsuvorov

    alexsuvorov

    Unity Technologies

    Joined:
    Nov 15, 2015
    Posts:
    327
    Hello Foriero.

    The most appropriate way to do this would be via styles using for example
    Code (css):
    1. #gameContainer canvas { ... }
    Let's consider two examples:


    1) Using default template.

    In this case you should make sure that the game footer (with logo, title and fullscreen button) fits the game width properly, so the whole width can be controlled by the webgl-content wrapper width. The embedding gameContainer div does not need to have any width or height at all, because it should just fit the wrapping div. Replace the following lines in the index.html in the default template:
    Code (html):
    1.     <div class="webgl-content">
    2.       <div id="gameContainer" style="width: %UNITY_WIDTH%px; height: %UNITY_HEIGHT%px"></div>
    for example with this:
    Code (html):
    1.     <div class="webgl-content" width="50%">
    2.       <div id="gameContainer"></div>
    Now make sure that the canvas aspect ratio is preserved, for example by adding the following lines to the TemplateData/style.css in the template:
    Code (css):
    1. #gameContainer canvas {
    2.   position: absolute;
    3. }
    4. #gameContainer:after {
    5.   padding-top: 56.25%;
    6.   display: block;
    7.   content: '';
    8. }

    2) Using minimal template.

    Pretty much the same idea, just the width can be controlled directly by the embedding gameContainer div:
    Code (html):
    1. <!DOCTYPE html>
    2. <html lang="en-us">
    3.   <head>
    4.     <meta charset="utf-8">
    5.     <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    6.     <title>Unity WebGL Player | Build</title>
    7.     <style type="text/css">
    8.       #gameContainer { width: 50%; }
    9.       #gameContainer canvas { position: absolute; }
    10.       #gameContainer:after { padding-top: 56.25%; display: block; content: ''; }
    11.     </style>
    12.     <script src="Build/UnityLoader.js"></script>
    13.     <script>
    14.       var gameInstance = UnityLoader.instantiate("gameContainer", "Build/build.json");
    15.     </script>
    16.   </head>
    17.   <body>
    18.     <div id="gameContainer"></div>
    19.   </body>
    20. </html>
    P.S. If you have more than one container, you might consider adding <div id="..." class="game-container"></div> and using game-container instead of #gameContainer in styles. Also note that 9/16 = 56.25%, not 52%.

    Let me know if this helps.
     
    Last edited: Dec 23, 2016
  32. Foriero

    Foriero

    Joined:
    Jan 24, 2012
    Posts:
    584
    Hi Alex, thank you for your answer. Now all clear and we have already wordpress plugin with more webgls possible on one page. Which is great. Each player with its own loader. :) And thank you for that 56.25 :) I was lazy to write it precise and happened that I mistyped 56 for 52. :) Have a great Christmas. Marek.
     
  33. Foriero

    Foriero

    Joined:
    Jan 24, 2012
    Posts:
    584
    KristianDoyle likes this.
  34. Foriero

    Foriero

    Joined:
    Jan 24, 2012
    Posts:
    584
    Hi Alex,

    We have last thing to solve. And it is how can we force your mobile dialog warning to be on top of our divs? Is it just Z ordering? If so what Z does your dialog has? If not how can we react to this dialog?

    Please see attachment.

    Thank you Marek.
     

    Attached Files:

  35. Foriero

    Foriero

    Joined:
    Jan 24, 2012
    Posts:
    584
  36. Foriero

    Foriero

    Joined:
    Jan 24, 2012
    Posts:
    584
    Hi Alex, I'm trying to make osx screensaver using WEBView to show fullscreen WebGL export from unity. So far it works nicely except your classes returns ( see attachment ) since it can not recognize browser. How can I suppress this message?
     

    Attached Files:

  37. alexsuvorov

    alexsuvorov

    Unity Technologies

    Joined:
    Nov 15, 2015
    Posts:
    327
    Hello Foriero.

    Probably the easiest way to do this would be to override the popup method of your game instance. For example, not to display the popup message at all if it does not require user interaction (i.e. when there is only one button).
    The popup method has 2 arguments: message and callbacks. The message represents the displayed text, and the callbacks is an array of buttons, describing their names and callbacks which should be called when the corresponding button is clicked (i.e. [{ text:"OK", callback:doThis }, { text:"Cancel", callback:doThat }]). So, basically, user interaction is only required if there is more than one button. Let's try the following:
    Code (JavaScript):
    1. var gameInstance = UnityLoader.instantiate("gameContainer", "Build/build.json", {
    2.   onProgress: UnityProgress,
    3.   popup: function (message, callbacks) {
    4.     if (!callbacks || !callbacks.length) {
    5.       return;
    6.     } else if (callbacks.length > 1) {
    7.       return UnityLoader.Error.popup(this, message, callbacks);
    8.     } else if (callbacks[0].callback) {
    9.       callbacks[0].callback();
    10.     }
    11.   },
    12. });
    This way you can override the popup method of a specific game instance directly from the embedding html. Let me know if this works for you.

    Note that you can use the very same idea to customize the popup message in order to match your page style. Just use the UnityLoader.Error.popup code from the UnityLoader.js as a template for your own overridden gameInstance.popup method and make the necessary adjustments. Note that UnityLoader.Error.popup(gameInstance, message, callbacks) has 3 arguments, while gameInstance.popup(message, callbacks) has 2 arguments. This is not a problem, because this keyword within gameInstance.popup call will refer to the gameInstance, which popup method has been called, so you can just replace gameInstance with this keyword (see the example above).

    I am not able test it at the moment because I do not use WordPress, but I think this is a good start. Just want to point out a small potential issue. Currently the new embedding scheme is still in beta state, which means that specification is not final. So WebGL content built with different versions of Unity might require different UnityLoader object, which might cause complications when embedding content built with different versions on the same page. This will be resolved a bit later after specification gets more complete. Ideally, all the builds should be versioned in the json file, so that it would be possible to have only one universal UnityLoader, compatible with all the previous versions of the builds.
     
    Last edited: Jan 3, 2017
  38. Foriero

    Foriero

    Joined:
    Jan 24, 2012
    Posts:
    584
    Hi Alex, we need desperately :) to be able to specify width and height as percentages :

    var gameInstance = UnityLoader.instantiate("gameContainer", "Build/TEST.json", {width: 100%; height:100%});

    Currently you allow only specify pixels which is a bit unfortunate for layout systems like wordpress or that mentioned screensaver. We are simply not able to make it 100% whatever we do in our xcode screensaver project.
     

    Attached Files:

    Knusper likes this.
  39. MartijnDekker

    MartijnDekker

    Joined:
    Feb 13, 2015
    Posts:
    8
    Hi,

    Can we make use of the UnityLoader to separate download for initializing ?

    We have a use case where an animation is shown to the user, in that time i want all of the webgl data to be downloaded.
    Then i want control when the initialization starts (thus will hang the browser for a couple of seconds).
    From what i see is that onRuntimeInitialized is called right after all compilation is completed.


    Any idea ?
     
  40. kou_yeung

    kou_yeung

    Joined:
    Mar 24, 2016
    Posts:
    28
  41. Nitrousek

    Nitrousek

    Joined:
    Jan 31, 2016
    Posts:
    38
    While I do appreciate the updates, I am now getting this error:
    https://i.imgur.com/fWCIQjw.png
    Console:
    https://i.imgur.com/DDMjnuE.png

    This project used to load without problems on 5.5 on a webserver. It works in firefox localhost without server. When I put it on the azure webserver it stops working. Seems not to be able to find webgl-XXX.json? Not even sure. I would appreciate any guidance with this.
     
  42. Schubkraft

    Schubkraft

    Unity Technologies

    Joined:
    Dec 3, 2012
    Posts:
    1,071
    Can you please submit a bug report with a repro project attached please?
     
  43. Nitrousek

    Nitrousek

    Joined:
    Jan 31, 2016
    Posts:
    38
    I can upload my whole project if that is necessary. It would be impossible for me to strip it to bare minimum for reproduction.
    I do think the problem lies within web.config which I am using, which ceased to work once the unity updated.

    Here's the web.config file.

    Code (CSharp):
    1. <configuration>
    2. <system.webServer>
    3. <staticContent>
    4. <remove fileExtension=".mem" />
    5. <mimeMap fileExtension=".mem" mimeType="application/octet-stream" />
    6. <remove fileExtension=".data" />
    7. <mimeMap fileExtension=".data" mimeType="application/octet-stream" />
    8. <remove fileExtension=".memgz" />
    9. <mimeMap fileExtension=".memgz" mimeType="application/octet-stream" />
    10. <remove fileExtension=".datagz" />
    11. <mimeMap fileExtension=".datagz" mimeType="application/octet-stream" />
    12. <remove fileExtension=".unity3dgz" />
    13. <mimeMap fileExtension=".unity3dgz" mimeType="application/octet-stream" />
    14. <remove fileExtension=".jsgz" />
    15. <mimeMap fileExtension=".jsgz" mimeType="application/x-javascript; charset=UTF-8" />
    16. </staticContent>
    17. </system.webServer>
    18. </configuration>
    Firefox dev version console log:
    https://i.imgur.com/hv4vG5J.png
     
    Last edited: Apr 5, 2017
  44. Schubkraft

    Schubkraft

    Unity Technologies

    Joined:
    Dec 3, 2012
    Posts:
    1,071
    Did you add the new ".unityweb" as mime type?
     
  45. Schubkraft

    Schubkraft

    Unity Technologies

    Joined:
    Dec 3, 2012
    Posts:
    1,071
    Can you please make a bug report about this?
     
  46. Nitrousek

    Nitrousek

    Joined:
    Jan 31, 2016
    Posts:
    38
    I did not do it yet, I'm really beginner into webapps in general.
    The example in the docs does not include it either, I believe it should be updated.

    PS. Just added it as mimetype, still does not work. Any examples of a working web.config please?
     
    Last edited: Apr 5, 2017
  47. Schubkraft

    Schubkraft

    Unity Technologies

    Joined:
    Dec 3, 2012
    Posts:
    1,071
    Code (csharp):
    1.  
    2. <?xml version="1.0" encoding="UTF-8"?>
    3. <configuration>
    4.     <system.webServer>
    5.         <staticContent>
    6.             <mimeMap fileExtension=".unityweb" mimeType="application/octet-stream" />
    7.         </staticContent>
    8.     </system.webServer>
    9. </configuration>
    10.  
    This works on my local IIS
     
    Nitrousek likes this.
  48. Nitrousek

    Nitrousek

    Joined:
    Jan 31, 2016
    Posts:
    38
    Well, the new unity build clearly does not work well with azure portal. Even a fresh project without anything in it, with the config file you sent, or the previous one I used, results in the same error. I'll keep trying new things and write here if I find a solution.

    What I tried includes but is not limited to:
    Changing/disabling compression methods
    Changing templates
    Creating a new project
    Creating a new server on azure
    Changing exceptions to full/none
    Copying files which are 404'd (not found) into main wwwroot folder, and assigning them in index.html
    Multiple modifications to web.config

    Nothing seems to be working so far.
    Example project: http://pukpuk.azurewebsites.net/

    I'll also submit a bug report at this point.
     
    Last edited: Apr 5, 2017
  49. Knusper

    Knusper

    Joined:
    Jun 5, 2014
    Posts:
    35
    I've got the same problem.
    We need to fit the canvas in the browser window. It's important that we don't want to use fullscreen but full browser window scale.
    Is there any possibility? We are new in web development. Unity-Version: 5.6 (stable)

    Thanks in advance!
     
  50. DukeRoderick

    DukeRoderick

    Joined:
    Dec 25, 2015
    Posts:
    15
    Hi Knusper,

    Here's some example code I have lying around that I've used to do a full screen, auto-growing canvas that will always match the size of the browser window.

    Here's my style.css

    Code (CSharp):
    1. #gameContainer canvas {
    2.     background-color: black;
    3.     position: absolute;
    4.     top: 0;
    5.     left: 0;
    6.     width: 100%;
    7.     height: 100%;
    8. }
    9. #gameContainer {
    10.     position: relative;
    11.     height: 100vh;
    12.     width: 100vw;
    13. }
    14.  
    15. .background {
    16.     margin: 0;
    17.     padding: 0;
    18. }
    19.  
    20. .webgl-content * {border: 0; margin: 0; padding: 0}
    21. .webgl-content {position: absolute; top: 50%; left: 50%; -webkit-transform: translate(-50%, -50%); transform: translate(-50%, -50%);}
    22.  
    23. .webgl-content .logo, .progress {position: absolute; left: 50%; top: 50%; -webkit-transform: translate(-50%, -50%); transform: translate(-50%, -50%);}
    24. .webgl-content .logo {background: url('progressLogo.Light.png') no-repeat center / contain; width: 154px; height: 130px;}
    25. .webgl-content .progress {height: 18px; width: 141px; margin-top: 90px;}
    26. .webgl-content .progress .empty {background: url('progressEmpty.Light.png') no-repeat right / cover; float: right; width: 100%; height: 100%; display: inline-block;}
    27. .webgl-content .progress .full {background: url('progressFull.Light.png') no-repeat left / cover; float: left; width: 0%; height: 100%; display: inline-block;}
    28.  
    29. .webgl-content .logo.Dark {background-image: url('progressLogo.Dark.png');}
    30. .webgl-content .progress.Dark .empty {background-image: url('progressEmpty.Dark.png');}
    31. .webgl-content .progress.Dark .full {background-image: url('progressFull.Dark.png');}
    32.  
    33. .webgl-content .footer {margin-top: 5px; height: 38px; line-height: 38px; font-family: Helvetica, Verdana, Arial, sans-serif; font-size: 18px;}
    34. .webgl-content .footer .webgl-logo, .title, .fullscreen {height: 100%; display: inline-block; background: transparent center no-repeat;}
    35. .webgl-content .footer .webgl-logo {background-image: url('webgl-logo.png'); width: 204px; float: left;}
    36. .webgl-content .footer .title {margin-right: 10px; float: right;}
    37. .webgl-content .footer .fullscreen {background-image: url('fullscreen.png'); width: 38px; float: right;}
    and here's my index.html

    Code (CSharp):
    1. <html lang="en-us">
    2.   <head>
    3.     <meta charset="utf-8">
    4.     <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    5.     <meta name="viewport" content="width=device-width, initial-scale=1.0">
    6.     <title>Unity WebGL Player | WebGLFullScreenExample</title>
    7.     <link rel="shortcut icon" href="TemplateData/favicon.ico">
    8.     <link rel="stylesheet" href="TemplateData/style.css">
    9.     <script src="TemplateData/UnityProgress.js"></script>
    10.     <script src="Build/UnityLoader.js"></script>
    11.     <script>
    12.       var gameInstance = UnityLoader.instantiate("gameContainer", "Build/webgl_fullscreen.json", {onProgress: UnityProgress});
    13.     </script>
    14.   </head>
    15.   <body class="background">
    16.     <div id="gameContainer">
    17.         <div id="canvas"></div>
    18.     </div>
    19.   </body>
    20. </html>
     
    engrarsal likes this.