The following changes have been introduced to the WebGL loader and templates in Unity 2020.1:
- Unity now generates a build-specific WebGL loader, which is stripped and optimized based on the selected player settings.
- WebGL templates can now use conditional directives and JavaScript macros.
- Build configuration is no longer stored in an external JSON file, but is embedded directly in the html.
- The WebGL instantiation function now uses the target canvas element as an argument, which gives the developers full control of the page layout.
- Unity 2020.1 supports server-friendly naming schemes for the build files.
Build-specific loader
In order to improve the loading performance, Unity 2020.1 will now generate a WebGL loader which is specific to the build. Unused code will be stripped from the loader, and the most efficient loading scheme will be chosen, based on the selected player settings.
Unity versions prior to 2020.1 generated a universal, build independent WebGL loader, which could be used to load other WebGL builds, created with different Unity versions.This approach simplified the embedding of WebGL builds in an html document, and was also very useful for embedding multiple builds, created with different Unity versions, on the same page.
However, using the universal loader to load just one specific build usually led to significant performance overhead. Firstly, the universal loader contains a lot of code which is never executed when loading a specific build (for example, it contains both gzip and brotli decompression fallbacks, although those compression methods are rarely used together). Secondly, the universal loader lacks optimizations which could be introduced for a build with specific configuration.
The changes implemented in the updated WebGL loader are intended to alleviate these problems. For comparison, the size of the minified loader in Unity 2019.3 is around 155 KB, while the loader in Unity 2020.1 can be stripped down to just 9 KB.
Template variables, macros and conditional directives
In previous Unity versions it was only possible to use %VARIABLE% tags in the html code, which were replaced with corresponding values when the template was preprocessed.
In Unity 2020.1 it is now possible to use JavaScript macros and conditional directives inside the template files. This allows you to perform advanced preprocessing of the source template files, based on the selected player settings.
Unity 2020.1 automatically preprocesses all the *.html, *.php, *.css, *.js and *.json files located in the template folder.
The following preprocessor variables can be used in JavaScript macros and conditional directives:
- COMPANY_NAME - the value of the Company Name field
- PRODUCT_NAME - the value of the Product Name field
- PRODUCT_VERSION - the value of the Version field
- WIDTH - the value of the Default Canvas Width field
- HEIGHT - the value of the Default Canvas Height field
- SPLASH_SCREEN_STYLE - is set to âDarkâ or âLightâ, depending on the selected Splash Style
- BACKGROUND_COLOR - background color in a form of a hex triplet
- UNITY_VERSION - current Unity version
- DEVELOPMENT_PLAYER - is set to true if the Development Build option is enabled
- DECOMPRESSION_FALLBACK - is set to âGzipâ, âBrotliâ or an empty string, based on the selected decompression fallback
- TOTAL_MEMORY - the initial size of the memory heap in bytes
- USE_WASM - is set to true if the current build is a WebAssembly build
- USE_THREADS - is set to true if the current build uses threads
- USE_WEBGL_1_0 - is set to true if the current build supports WebGL1.0 graphics API
- USE_WEBGL_2_0 - is set to true if the current build supports WebGL2.0 graphics API
- USE_DATA_CACHING - is set to true if the current build uses indexedDB caching
- LOADER_FILENAME - the filename of the build loader script
- DATA_FILENAME - the filename of the main data file
- FRAMEWORK_FILENAME - the filename of the build framework script
- CODE_FILENAME - the filename of the WebAssembly module
- MEMORY_FILENAME - the filename of the memory file
- SYMBOLS_FILENAME - the filename of the JSON file containing debug symbols
- BACKGROUND_FILENAME - the filename of the background image
JavaScript macros
JavaScript macros are blocks of JavaScript code, included in the template files, which are surrounded by triple curly brackets. When these code blocks are found in preprocessed template files the preprocessor evaluates them and replaces the macros with the result of the code evaluation. Evaluated JavaScript code can use internal preprocessor variables which are assigned at build time according to the values supplied by the Editor.
As an example, see the following line from the index.html file used in the Default template:
<div id="unity-build-title">{{{ PRODUCT_NAME }}}</div>
If the value of the Product Name in the Player Settings is set to âMy WebGL Gameâ, then the internal preprocessor variable PRODUCT_NAME will be also set to âMy WebGL Gameâ value, and in the output index.html file the mentioned line will be transformed into:
<div id="unity-build-title">My WebGL Game</div>
Now letâs consider a more complex example from the same index.html template file:
canvas.style.background = "url('" + buildUrl + "/{{{ BACKGROUND_FILENAME.replace(/'/g, '%27') }}}') center / cover";
If the target build folder is called âLetâs try WebGLâ, and a background image is provided in the Player Settings, then the internal preprocessor variable BACKGROUND_FILENAME will be set to âLetâs try WebGL.jpgâ value, and in the output index.html file the mentioned line will be transformed into:
canvas.style.background = "url('" + buildUrl + "/Let%27s try WebGL.jpg') center / cover";
JavaScript macros can be used to preprocess the values supplied by the Editor (that is, escape single quotes as shown in the above example). JavaScript macros are not limited in complexity, they can include multiple operators, loops, functions, and any other JavaScript constructs.
Conditional directives
In Unity 2020.1 it is now also possible to use conditional #if, #else, #endif directives in template files.
Example of a conditional group:
#if EXPRESSION
// this block will be included in the output if EXPRESSION has truthy value
#else
// this block will be included in the output otherwise
#endif
JavaScript expressions evaluated in conditional directives are not limited in complexity, they can include brackets, logical operators and other JavaScript constructs. Conditional directives can be nested.
This is an example conditional directive from the index.html file used in the Default template:
#if SYMBOLS_FILENAME
symbolsUrl: buildUrl + "/{{{ SYMBOLS_FILENAME }}}",
#endif
If the Debug Symbols option is enabled, the Development Build option is disabled, the target build folder is called âLetâs try WebGLâ and Compression Format is set to Disabled, then the internal preprocessor variable SYMBOLS_FILENAME will be set to âLetâs try WebGL.symbols.jsonâ, and in the output index.html file the mentioned block will be transformed into:
symbolsUrl: buildUrl + "/Let's try WebGL.symbols.json",
If Debug Symbols option is disabled or Development Build option is enabled, then the internal preprocessor variable SYMBOLS_FILENAME will be set to an empty string, and the mentioned block will be completely stripped from the output index.html.
Custom user variables
In previous Unity versions it was necessary to use the âUNITY_CUSTOM_â prefix to mark a custom user template variable. This is no longer necessary in Unity 2020.1, as the template parser automatically finds all the user variables.
When a WebGL template is selected in the Player Settings window, Unity parses the template files looking for JavaScript macros and conditional directives. JavaScript variables used in macros and conditional directive expressions, which are not declared in the template code and are not internal preprocessor variables, are treated as a custom user variables and are automatically added as editable fields to the Player Settings.
For example, if you would like to control the title of the generated index.html page directly from the Player Settings you can do this by modifying the line of the index.html in your custom template as shown:
<title>{{{ PAGE_TITLE }}}</title>
Now, if you reopen the Player Settings window and reselect the template, the template will be re-parsed, a new PAGE_TITLE variable will be found and added as an editable field directly to the Player Settings.
Instantiation of the build
In Unity 2020.1, the following important changes have been introduced for the instantiation function:
- The instantiation function now directly accepts a element as an argument. This gives the developers full control of the page layout.
- The build configuration object is no longer stored in an external JSON file, but is embedded directly in the html code, and is used as an argument for the instantiation function. This increases the size of the JavaScript code which is added to the embedding html, but improves the loading performance, because it eliminates the need to perform an additional server request.
- Instantiation function now returns a Promise object.
The new instantiation function can be used in the following way:
createUnityInstance(canvas, config, onProgress).then(onSuccess).catch(onError);
where:
- canvas is a element which will be used to render the game.
- config is an object which contains build configuration, specifically, code and data urls as well as product and company name and version. Configuration object can be defined using the following code:
var buildUrl = "Build";
var config = {
dataUrl: buildUrl + "/{{{ DATA_FILENAME }}}",
frameworkUrl: buildUrl + "/{{{ FRAMEWORK_FILENAME }}}",
codeUrl: buildUrl + "/{{{ CODE_FILENAME }}}",
#if MEMORY_FILENAME
memoryUrl: buildUrl + "/{{{ MEMORY_FILENAME }}}",
#endif
#if SYMBOLS_FILENAME
symbolsUrl: buildUrl + "/{{{ SYMBOLS_FILENAME }}}",
#endif
streamingAssetsUrl: "StreamingAssets",
companyName: "{{{ COMPANY_NAME }}}",
productName: "{{{ PRODUCT_NAME }}}",
productVersion: "{{{ PRODUCT_VERSION }}}",
};
- onProgress(progress) callback is called every time when the download progress updates. It is provided with the progress argument, which determines the loading progress as a value from 0.0 to 1.0.
- onSuccess(unityInstance) callback is called after the build has been successfully instantiated. The created Unity instance object is provided as an argument. This object can be used for interaction with the build.
- onError(message) callback is called if an error occurs during build instantiation. An error message is provided as an argument.
Decompression fallback and file naming schemes in Unity 2020.1
The newly introduced Decompression Fallback option, which can be found under the Publishing Settings, has a significant impact on the loading performance. When the Decompression Fallback option is enabled, the build files will have a .unityweb extension, and the loader will include a JavaScript decompressor for the selected compression method. The JavaScript decompressor will automatically be used in cases where the downloaded content fails to be decompressed natively by the browser. This option can be useful if you donât have access to the server configuration or are not able to append specific http headers to the server response. However, using this option will also result in increased size of the loader and inefficient loading scheme for the build files (for example, WebAssembly streaming can not be used together with decompression fallback).
When the Decompression Fallback option is disabled (which is the default), the build files will have an extension corresponding to the selected compression method (i.e. .gz or .br). Additionally, the loader will try to use WebAssembly streaming by default. When hosting such a build on a server, the following http headers should be added to the server responses in order to make the build load correctly:
- .gz files should be served with a Content-Encoding: gzip response header.
- .br files should be served with a Content-Encoding: br response header.
- .wasm, .wasm.gz or .wasm.br files should be served with a Content-Type: application/wasm response header.
- .js, .js.gz or .js.br files should be served with a Content-Type: application/javascript response header.
Note that compressed WebGL builds which donât include decompression fallback can not be loaded from the file:// urls due to the lack of the necessary response headers. In order to run such a build locally, you can use the Build and Run option or a local web server with properly setup response headers.