WebGL and Unity in 2019

WebGL and Unity in 2019

I’ve recently updated a few of my games to Unity 2019.1 (at least one from as far back as Unity 5.5), and made some improvements to the web page that they are embedded in – including adding custom loading graphics, and disabling the Unity mobile warning. I thought I would share my experience in this blog post and provide some snippets of code that might help others working with the Unity WebGL exporter. If you want to check out my games in action you can find them here: Quantum Foam Games.

Perhaps the biggest change when it comes to WebGL export in Unity 2019.1 is that WASM (Web Assembly) is now the only choice you have. There are workarounds available to anyone that also needs ASM.js versions of their game exported (if you must support ancient versions of Chrome, or – ugh! – IE 11). I personally opted to just use WASM on my site going forward. It works well on all up to date versions of Chrome, FireFox, Safari and Edge, including mobile versions. According to caniuse.com the coverage for WASM is at 86%, which is good enough for me. As to performance, I don’t have any measurements, but subjectively I compared an old ASM.js build to a new WASM one in Chrome loading from local disk, and the WASM build feels like it loads instantly, while the old ASM.js build hangs for several seconds as it compiles the multi-megabyte Javascript file. So the startup performance benefit is certainly there (others, including the official Unity blog have posted numbers that also back this up).

The other big change (and this must be recent, since the Unity documentation doesn’t actually mention this yet) is that you no longer have to specify how much memory your game will need (making use of the new dynamic heap grow tech that was added to Emscripten). This is a big deal, especially for small indie devs that don’t really want to spend a huge amount of time profiling their game. Previously you had to specify the maximum heap size for your game, which usually just meant picking some plausible sounding number like 256MB and calling it a day, but then you might be wasting a bunch of then player’s RAM for no reason, or players might find that the game crashes after playing for a while because the game suddenly runs out of memory. So the onus was on the developer to profile your game, figure out how much memory it would typically use, and test it extensively to figure out the perfect number. Well, no more! Unity will now grow the heap dynamically and as long as the user has enough RAM available your game will keep on running. Fantastic!

The bad news is that nothing much else has really changed in the last two years or so. WebGL builds are still huge (5-10MB for a small game). There are still issues with graphical fidelity compared to other build targets. You still can’t use linear color space and some other features because then you are locked to WebGL2.0, and in all this time Apple still hasn’t got that working in Safari (and completely giving up on the iOS market is just too big of a sacrifice for me, and I think for most other devs too). Now WebGPU is in the process of being created as next-gen replacement for WebGL, which might improve things at some point, but we are still over a year away from that becoming reality. The other cool thing on the horizon is Project Tiny from Unity, which promises to build truly web-first builds from within Unity, with tiny builds (just a few hundred KB for a small game). Unfortunately right now that only supports Javascript (a big reason for using Unity rather than something like Phaser is so that I get to use C# and not Javascript), and it only supports 2D for now. However, C# support and 3D capabilities are planned for Project Tiny in the future, and once those start to be added I’ll be sure to take another look at it as an alternative to the WebGL export option.

OK, now onto some code snippets to help anyone else working on Unity WebGL builds. First let’s look at removing the mobile warning. I know the Unity team mean well with this, but really there should be an option to disable this somewhere in the WebGL build options, especially in 2019 where most mid-high end mobile devices actually will run the Unity WebGL content very well. Even when testing on the 2018 budget iPad (which only has an anemic 2GB of RAM) my games ran without a hitch (well except for the missing WebGL2.0 support in Safari, get that fixed please Apple!). With most devices from the last 2-3 years having at least 3GB of RAM or more most Unity WebGL games should run pretty well on most mobile devices. Anyway, disabling the check is quite easy. Simply add the following two lines of JS code immediately before the code that instantiates the game (the line looks like “var unityInstance = UnityLoader.instantiate…) in index.html:

UnityLoader.Error.Handler = function() {};
UnityLoader.compatibilityCheck = function(e,t,r) { t(); };

And that’s it! You may want to comment out the line that sets the error handler to an empty function while you are testing your game, but once the game is live I would leave that in, to prevent showing any confusing error pop-ups to your players that they won’t be able to do much with anyway.

Next, you will probably want to create a custom loader for you game(s). The default one is rather drab and includes the big Unity logo branding. Again look for the line of code that instantiates the Unity instance. Now modify it to and change the part that says {onProgress: UnityProgress} to point to your own Javascript function (e.g. {onProgress: MyProgress}). Mine looks like this (note that I use JQuery since I have it included on my pages already for other tasks as well, if you don’t want to include it you can easily modify the code to work without it):

function MyProgress( unityInstance, progress ) {
    if( !unityInstance.Module {
        return;
    }
    if( progress == 1 ) {
        $('.loader').hide();
    }
    $('#progress').text( (progress*100)+'%' );
}

The code should be self-explanatory, but the progress number passed into the function is a number from 0-1 that represents how far the game has loaded. You can use this number to animate a progress bar or just show the number as a percentage to the user as I have done. Once progress hits 1 the game is loaded and you can hide the element that you have used as the loading indicator (probably a <div> with some text and/or images in it, in my case a <div> with the “loader” class on it).

Next, you will likely want your game to fill out the entire window, or at least to adapt based on the size of some element on the page. The default Unity WebGL template just forces your game to sit in the middle of the page at a fixed size. While that does make it a little easier since you don’t have to code your game to adapt to the screen size, it is less than ideal. Once you’ve made sure your game works at variable window sizes you can add the following JavaScript to your page:

function setDimensions() {
    var gameContainer = $('#gameContainer');
    var unityCanvas = $('canvas');

    unityCanvas.width( gameContainer.innerWidth() );
    unityCanvas.height( gameContainer.innerHeight() );
}

var resizeTimer;

$(window).on( 'resize', function(e) {
    clearTimeout( resizeTimer );
    resizeTimer = setTimeout( setDimensions, 150 );
});

This code again assumes that you have JQuery loaded into your page, and that you have a <div> element with the id set to “gameContainer” on your page, that is styled to respond to the size of the window (on my site I used some FlexBox CSS magic to fill out the <div> to cover the entire page, except for a thin header bar along the top). The code above takes care of resizing the image and ensures that it only actually resizes the Unity canvas when the user has stopped changing the size of the window, as this is an expensive operation.

Finally I coded up a simple PHP template that would load up any WebGL game, so that I could easily maintain a consistent look across all of my games. I also enabled “Name files as hashes” and “Data caching” in the WebGL publishing settings. This is a good idea as it allows you to cache the game with the user so it loads super fast if they return to it, but also ensures that if anything is changed that it is re-downloaded (if you don’t hash the file names you are at the mercy of the browser deciding if the game data is out of date or not, meaning the player may not get the latest version or worse yet may end up with files from multiple different versions resulting in the game crashing). I didn’t want to have to edit my PHP template each time I uploaded a new version though, and I wanted it to work with all of my games with no modification so I added the following code to find the JSON configuration file, and the JavaScript that contains the loader (UnityLoader.js when you don’t ouput hashed file names):

$unityloaderfile = basename(glob( 'Build/*.js' )[0]);
$webgljsonfile = basename(glob( 'Build/*.json' )[0]);

$unity_cfg = json_decode( file_get_contents( "Build/$webgljsonfile" ), true );

I also load the JSON configuration file and use that to get the name and version number of the game to display in my header bar. You can then include the loader like so:

<script src="Build/<?=$unityloaderfile;?>"></script>

And instantiate the UnityInstance like this:

var unityInstance = UnityLoader.instantiate("gameContainer", "Build/<?=$webgljsonfile;?>", {onProgress: MyProgress});

Easy. This way you don’t have to mess around with creating custom HTML templates for Unity to use in the build process, and when you update your game all you have to do is replace the contents of your Build directory with the new files (just be sure to remember to delete the old files first!).

If you are wondering how to style the CSS to fill the window, just load up any of the games on my site (https://www.quantumfoamgames.com) and inspect the pages, it’s all right there in game.css. Hopefully this helps someone else when trying to figure out the best way to work with the Unity WebGL exporter. I intend to play around with it some more, and to work on some more games soon, and I will try to document my findings in some future blog post(s).

Comments are closed.