Unity AssetBundles

For the game that I’m currently working on, I decided to implement support for Unity’s AssetBundles. Right out of the gate, I jumped into the API and started implementing it in my game, since it seemed so straightforward. Lo and behold, I hit the wall of failure. The concept is clear, but getting it all implemented such that it didn’t slow down my day-to-day development flow was a challenge. Let me explain how I got through things. But first…

What are AssetBundles?

Go ahead and skip this section if you already know what they are, but for the rest of us, here is a brief explanation.

An AssetBundle is a library of assets that can be stored as a file that is hosted separately from the game build. Before AssetBundles were more accessible for developers to work with, any assets that you used in your game (i.e. images, meshes, materials, animations, audio, etc.) needed to be included in the project as part of the game build. That’s fine and all, but as the number of assets in the game grew, it also meant the amount of possibly unused assets were unnecessarily loaded into memory, wasting a lot of system resources, thereby bogging down your game build, and not to mention your entire system.

To alleviate this problem, developers would use the Resources folder to manage their own asset loading, as Unity treats this folder differently from the rest of the assets. This is more of a traditional convention with resource management, in that developers can load their assets “on-demand”, and thereby be a little bit more picky about what assets are loaded into RAM, at any moment in time during the game’s run. This also meant that developers are also responsible for unloading the assets from RAM.

Gamedevs still use the Resources folder (I’ve seen the files in a few popular shipped Unity games), but Unity Technologies has since identified several problems with using the Resources folder for a production release, that can be found here.

So Unity Technologies recommends that AssetBundles are the way to go for packaging content, especially if you plan on delivering that content alongside your game build, or some time in the future with patches and content updates. There’s plenty more information in Unity’s documentation on AssetBundles, so I’m just going to move on now to the more “in-practice” side of things; areas that don’t really seem to be covered very well in Unity documentation.

Multiple Sources

The challenge with AssetBundles isn’t so much the actual authoring and loading of them. It’s that, every time you make a change to an asset in the AssetBundle, you have to rebuild. This completely kills your iteration time on editing those assets. Is there no way around this!? Well… There are ways to expedite the testing process of assets that you have included in an AssetBundle, but it really depends on what stage of implementation you are on your game. This determines which method you should use for testing your assets.

You could be testing the asset:

  1. In the Editor, as a loose asset. This tests the asset itself, but not the AssetBundle that contains it.
  2. In the Editor, as an asset in an AssetBundle. This tests the asset in the AssetBundle, but in the context of the Editor. Typically, the players of your game will only have a standalone build. Not something tied to the editor.
  3. In a published Development Build, from an AssetBundle. This tests the asset in the AssetBundle in a standalone build, with some extra debug tools, to track down any errors that may not show up when testing in the Editor.
  4. In a published Release Build, from an AssetBundle. This tests the asset in the AssetBundle in a standalone build which would be released to players, with absolutely no extra debug help, other than maybe some output logs. If a bug happens here that cannot be replicated in one of the previous 3 cases, it can be difficult to fix.

These four ways to test an asset and its AssetBundle can get confusing really quickly. To alleviate this confusion, Unity had provided the AssetBundleManager, which is a set of scripts that wrap up the AssetBundle API into a nicer high-level API that abstracts away the various methods an asset can be loaded, from an AssetBundle. Well, that’s nice!….. If it worked.

AssetBundleManager

“If it worked? What do you mean?”

Heh. I eventually got it working, and you can scroll down to see how if you want to skip this rant.

I’m running Unity 5.6.3f, which was released in August 2017. That was a couple of years ago, as of this writing. Within that time, AssetBundleManager has been marked as deprecated by Unity, yet there is no other alternative* in its place (other than dealing with the AssetBundle API directly), which is quite confusing to us users who are just starting out with it. Straight off of bitbucket, the AssetBundleManager will not work, without a few changes and configurations. This puts developers who would like to develop the “right” way by using AssetBundles in a sort of limbo, and I wouldn’t blame any developer who feels like they got shortchanged by this sort of messaging from Unity’s documentation.

So, the only course of action is to fix the AssetBundleManager to my needs, as suggested by Unity, but is by no means anything that I had planned on spending my time on.

* There is a new Addressable Asset System which provides far more than what AssetBundleManager gives. It has recently gotten out of “preview” status, so adoption of this new system is still happening. 

Okay, end rant.

Back to Basics

I certainly want to utilize the benefits of Unity’s AssetBundleManager. It has everything I need to alleviate the inefficiencies of testing AssetBundle builds. I made the mistake of immediately integrating the AssetBundleManager into my game, and that seriously set me back, because I started to get so confused with the various asynchronous load handlers. After much failure, I had to get back to basics, so I downloaded Unity’s AssetBundleDemo and started from there.

What follows is pretty much a walkthrough of the demo.

Get the AssetBundleDemo from bitbucket here. It contains the AssetBundleManager.

Create a new project, and put the AssetBundleDemo contents into the project’s Assets folder.

Also, get the AssetBundle Browser from github as recommended on this Unity page. Drop this in your project’s Assets folder too.

The scene of interest is AssetLoader.unity. Load that up, and we’ll step through what to do.

First, this is what the AssetBundleManager menu looks like.

The three we’ll be using are:

  • Build Assets
  • Simulation Mode
  • Local AssetBundle Server

I don’t think I had a need for “Build AssetBundles from Selection”, so I have not tried it. And I have no idea what “Build Player (for use with engine code stripping)” is. I tried it, and did not get any good results, and I found it unnecessary.

This is what the AssetBundle Browser UI looks like:

The Build page in the Asset Bundle Browser Tool

Follow Along

This assumes you are developing on Windows, so, sorry in advance to all you other development platforms.

In the following tests, we should see a Unity cube appear on the screen. If it doesn’t, then the AssetBundles failed to load.

The asset of interest is called MyCube, and it is assigned the “cube-bundle” AssetBundle group.

In the scene, the Loader object has a LoadAssets.cs script, which sets up the AssetBundleManager and attempts to load the AssetBundle and the cube object from that AssetBundle. 

The Unity Editor

Editor with No Changes

The very basic test you can do is in the Editor. Press Play. Notice that no cube shows up. 

This is expected. Since we have not built an AssetBundle yet, there is nothing to load.

Editor with Simulation Mode

The next test you can do is activate Simulation Mode, and press Play. The cube shows up.

Simulation Mode is great for very quick tests of the assets that are labeled for an AssetBundle. Use this mode when iterating on edits of an Asset. But in reality, this mode doesn’t even check for the AssetBundle itself, bypassing that entire system. It simply loads the asset directly from your Assets tree. This isn’t a very comprehensive test for a final build. So, be warned.

Editor with the Local Server

Next test, turn off Simulation Mode, and turn on the Local AssetBundle Server.

Go into Windows Processes, under Unity processes, notice mono.exe. That’s the Local Assetbundle server.

Play the game, and notice that nothing loads. See the error message. The game is connected to the local server, but it fails to download the AssetBundles.

This is because we still have not built any AssetBundles, so we’ll do that next. Build AssetBundles, and keep the Local Server enabled.

Running the game in the Editor now shows the Cube.

Great! So we can now load the cube using the AssetBundleManager in the Editor, using either Simulation Mode or the Local Server when AssetBundles are built.

Standalone Builds

Now let’s publish some builds. For now, disable the Local Server from the Editor menu.

For simplicity’s sake, we are going to use Unity’s StreamingAssets folder to automatically have the AssetBundles copied to the build when the Player is built.

For convenience, the AssetBundle Browser has a check box for pushing to StreamingAssets, so we’ll use that to build our AssetBundles instead of the AssetBundleManager menu.

Change the output path from AssetBundles/WindowsStandalone to AssetBundles/Windows. The LoadAssets script is expecting this path, simply due to the way AssetBundleManager has been written.

All of this special-case treatment of the AssetBundleManager versus AssetBundle Browser, in regards to StreamingAssets and output paths, was definitely part of my confusion. It’s as if Unity Technologies just stopped what they were doing and checked it in without a second thought about how the output will be affected. Although there’s adequate warning about the AssetBundleManager being deprecated, I’m sure it’s led to a lot of frustration for anyone running this demo.

Development Build with StreamingAssets

Click on Build in the AssetBundle Browser. This copies the AssetBundles to the StreamingAssets folder. (As a side note, notice that the StreamingAssets output is not contained in a /Windows subfolder. This is something that you would also have to change in the AssetBundle Browser scripts if you want to keep the platform-specific folder organization.)

Okay, make a standalone build, with the Development Build Option on (I like to publish my builds to an “/out” directory”). 

Verify that the build has written out the AssetBundles in the StreamingAssets folder.

Run the build. Notice no cube appears.

Also notice the debug error log at the lower left. This is very helpful in debugging why assetbundles don’t load, among other errors.

This is the same error as the one we got when running the game in the Editor.

Development Build with the Local Server

Activate the Local Server from the Unity Editor, and then re-run the standalone Development Build.

Now, the cube loads. This development build is communicating with the Local AssetBundle Server. The content that is being served is contained in the AssetBundles directory in the root of your project, and NOT the StreamingAssets folder. This is the snippet of LoadAssets.cs that uses the Local Server:

    // Initialize the downloading URL.
    // eg. Development server / iOS ODR / web URL
    void InitializeSourceURL()
    {
        // ...
#if DEVELOPMENT_BUILD || UNITY_EDITOR
        // With this code, when in-editor or using a development builds: Always use the AssetBundle Server
        // (This is very dependent on the production workflow of the project.
        //      Another approach would be to make this configurable in the standalone player.)
        AssetBundleManager.SetDevelopmentAssetBundleServer();
        return;
#else
// ...
#endif }

Convenient for testing, but not at all reflective of a completely independent standalone build.

Release Build

And finally, create a full Release build (Disable the Development Build option, and rebuild to a Release executable).

The contents of this build should be pretty much the same as the Development Build. The main difference now is that we are now looking at the StreamingAssets folder, regardless of if we have the Local AssetBundle Server running.

Run the build. Notice no cube shows.

Open the output_log.txt, notice the error.

It couldn’t load the manifest. Whoops, I just threw in new vocabulary. The manifest AssetBundle is the entry point into loading the rest of the assets in that AssetBundle. In this case, it holds the name of the platform: “Windows”.

The Fix

This brings us back to modifying the LoadAssets script. Open it up in an IDE (You’re using Visual Studio, right? Because you should be…).

Find this block of code in InitializeSourceURL():

AssetBundleManager.SetSourceAssetBundleURL(Application.dataPath + "/"); 

Comment this out, and change it to this instead:

AssetBundleManager.SetSourceAssetBundleDirectory("/"); 

What this does is set the AssetBundle path to the root of the StreamingAssets folder.

Before rebuilding the release build with the above script change, be sure to close output_log.txt if you have it open. Unity can’t build to the same output directory if any file in that path is open.

Re-build the release build, and run the executable. The cube shows up!

The Unity documentation for the AssetBundle Browser states that the StreamingAssets path is useful for testing, but should not be used in production. I’m not sure why this would be the case though; I plan on using StreamingAssets for my project.

What I described above is only one solution to getting this to work, so you may have to modify the solution for your own build pipeline if you choose to use these scripts as a base.

Review

Here are some guidelines for when to use what mode of testing:

  1. Test in Simulation Mode when testing loose assets marked as AssetBundle files in the editor
  2. Test with the Local Server to test published AssetBundle files (they must be published to the AssetBundles folder at the root of your project).
  3. Test with the Local Server (or published AssetBundles in StreamingAssets) also with published Development Builds.
  4. Test the Final Build and use the output_log.txt to debug any problems at this point. But hopefully, all the previous steps helped that this stage is not a problem. But, since loading goes through completely different paths, there is still a chance that the AssetBundles do not load at any stage of this development.

Going through all these modes should give you a clue on how and when to use these different techniques. Throughout the development of your game and as you create more content, you’ll want to test on the simplest level, via Simulation Mode, all the way to the published non-development Release Build, with the output_log.txt file.

As you can see from the entire process above, it adds a significant amount of overhead to your development workflow. You can always build tools to automate some things, like auto-assigning AssetBundle ids to certain assets, but ultimately, you (or someone else) will still have to test the assets on every stage of development, from inside the Editor to a full-blown Release build. And this essentially introduces a lot more chances for bugs to show up, and I hate bugs which show up in a Release build, that cannot be reproduced in a Debug build. Ugh!

NOTE: This article only covers the very basic loading method for AssetBundles that get locally published alongside the game build, using StreamingAssets. AssetBundles can also be loaded from other sources, like a web server, for example.

The Future of AssetBundles

In recent months, Unity Technologies has spent a large amount of effort to get the new Addressable Asset System to a solid, production-ready state. The Addressables system is meant to alleviate all the issues that AssetBundles have. It’s meant to replace the AssetBundleManager and Browser and add a lot more robustness to the inherent difficulties of dealing with resource management. Some developers have had success with the current state of Addressables, and others have not.

All in the name of preparing your game for a “commercial” release. Of course, if you’re not looking to commercially release your game, I probably could not recommend using AssetBundles. It’s just a large amount of effort that takes away from where your greatest effort should be: making the actual game. In this case, the Resources folder and direct references are still suitable options.

So, whether or not you choose to integrate AssetBundle support in your game, remember to…

Make it fun!

This entry was posted in Dev, Programming. Bookmark the permalink.

Leave a Reply

Your email address will not be published.

This site uses Akismet to reduce spam. Learn how your comment data is processed.