The Simplest Unity Bundling Example I Could Make

Asset bundles in Unity can be tricky. I’ve spent a lot of time at work developing a pretty slick bundling system. The system pulls the latest from our git repo, then uses manifests to pull in new procedural assets, then generates all the bundles for each level, then uploads them into versioned folders on Amazon S3.

Most big projects will need something like this.

But it’s probably best to start with something simpler. The system I’ve built for work is probably 500 – 1000 lines of code with everything included. This weekend I wanted to find the least amount of code with which I could make a working bundles system.

This system has no extra features. It doesn’t support simulation mode. It doesn’t support procedural assets. It doesn’t support multiple simultaneous downloads. It doesn’t support viewing download progress. But it does work.

Here’s how to get Unity asset bundles working in under 200 lines of code.

Download the AssetBundleManager from the Asset Store

You don’t need the examples. You’re really only using this for the UI features added to the Editor, but some of the included classes in that package are also a good starting point.

Assign assets to bundles

When you select an asset, you should see a drop down all the way at the very bottom of the inspector. Use that dropdown to create a new asset bundle, then add your assets to it.

Create editor script for clearing bundle cache

When you are testing bundles, you will need an extra menu item to delete your local cache. Make an Editor folder somewhere and drop this script in it.

using UnityEngine;
using UnityEditor;

public class BundleOptions
{
    /// 
    /// Delete all cached bundles. This is necessary for testing.
    /// 
    [MenuItem("Assets/AssetBundles/Clear Bundle Cache")]
    static void LogSelectedTransformName()
    {
        if( Caching.CleanCache() )
        {
            Debug.Log("All asset bundles deleted from cache!");
        }
        else
        {
            Debug.LogWarning("Unable to delete cached bundles! Are any bundles in use?");
        }
    }
}

You will want to click Assets > AssetBundles > Clear Bundle Cache between each generation of bundles to make sure that you are using the latest version in editor.

Create a BundleManager class

Your BundleManager class will download bundles and allow the program to access asset bundles. If you expand this example, then this will be the center of your work.

using UnityEngine;
using System.Collections;
using System.Collections.Generic;

public class BundleManager : MonoBehaviour
{
    // the web server where your bundles are stored - you can test this locally using file://c:/path-to-bundles/etc
#if UNITY_EDITOR
    private static string baseBundleUrl = "file://c:/example/Unity/MyGame/AssetBundles";
#else
    private static string baseBundleUrl = "http://www.example.com/MyGame/AssetBundles";
#endif

    // this dictionary will store references to each downloaded asset bundle
    private static Dictionary bundles;

    /// 
    /// Get a reference to an AssetBundle instance that has already been downloaded.
    /// 
    /// This should be used when you want to load an asset from a bundle.
    /// 
    /// the name of the bundle to get
    /// a reference to an AssetBundle or null if that bundle has not yet been downloaded or cached
    public static AssetBundle GetBundle(string name)
    {
        if( bundles != null && bundles.ContainsKey(name) )
        {
            return bundles[name];
        }
        return null;
    }

    /// 
    /// Very simple method for downloading a single bundle.
    /// Extensions to this method may include a progress monitor and a callback for when the download is complete.
    /// 
    /// the name of the bundle you want to download
    /// an IEnumerator to be run as a Coroutine
    public static IEnumerator DownloadBundle(string bundleName, System.Action downloadCallback = null)
    {
        Debug.Log("Attempting to load bundle " + bundleName + "...");

        WWW www = WWW.LoadFromCacheOrDownload(getFullBundleUrl(bundleName), 1);

        yield return www;

        // if there was a download error, then log it
        if( !string.IsNullOrEmpty(www.error) )
        {
            Debug.LogError("Error while downloading bundle " + bundleName + ": " + www.error);
        }
        else // if there was no download error
        {
            // try to get the downloaded bundle
            AssetBundle newBundle = www.assetBundle;

            // if the bundle is null, then log an error
            if( newBundle == null )
            {
                Debug.LogError("Unable to save bundle " + bundleName + ": Bundle is null!");
            }
            else // if a valid bundle was downloaded
            {
                if (bundles == null)
                    bundles = new Dictionary();

                // store a reference to that bundle in the dictionary
                bundles[bundleName] = newBundle;

                Debug.Log("Successfully loaded " + bundleName + ".");
            }
        }

        // if there is a downloadCallback, then call it
        if( downloadCallback != null )
        {
            downloadCallback();
        }
    }

    /// 
    /// Return a string representation of the platform that will go into the bundle path.
    /// 
    private static string getBundlePlatform()
    {
        if (Application.platform == RuntimePlatform.Android)
        {
            return "Android";
        }
        else if( Application.platform == RuntimePlatform.WindowsEditor )
        {
            return "Windows";
        }

        // maybe this is some strange version of Android? Need this for Kindle.
        return "Android";

        // do not support other platforms
        //throw new System.Exception("This platform is not supported!");
    }

    private static string getFullBundleUrl(string bundleName)
    {
        return baseBundleUrl + "/" + getBundlePlatform() + "/" + bundleName;
    }

    // Use this to initialize the bundles dictionary
    void Start ()
    {
        if (bundles == null)
            bundles = new Dictionary();
    }

}

Modify that script by replacing baseBundleUrl with the place where your bundles are stored locally for development and remotely for production.

You will also need to hook this script into the program somehow. You can attach it to a GameObject if you like, but that isn’t strictly necessary. All you really need to do is start a coroutine to run the DownloadBundle method, then call GetBundle to get the download. Then just call the LoadAsset method on the asset you want to instantiate.

Generate bundles

When you click Assets > AssetBundles > Build AssetBundles, it will generate bundles for whatever platform is selected. You will need to generate bundles for your development machine and for your target platform. Make sure to modify the getBundlePlatform method to support your platform.

And…. ?

And that’s it. Asset bundles are not really that complex when you boil them down. For my music apps, I just want them to store a few extra sound files that I don’t want to distribute with the executable. So a system like this works fine. I just start the download when the app starts.

Of course, for larger projects you will need many more features. Versioning bundles is very important for the development and build processes. Also you will want to show a progress bar on the screen. And you will want to load entire levels into bundles, which is a little more tricky. But hopefully this will get you started on the right track.

06. June 2016 by evan
Categories: Software | Tags: , , , , , , , , , | Leave a comment