Assetbundle DownloadHandlerFile

Hello.
We got this script that we have been using to download whole scenes as assetbundles. The problem with this script method, is that it gives the user little control over data. To clear up space, we have a “clear cache” button, which removes everything. So to get more data control, i wanted to rewrite using DownloadHandlerFile, and create folders to store bundles in. So each bundle the user downloads, gets its own folder, and downloads the bundle into said folder. I would then be able to get all folders in “SavedAssetData” into a list, and give the user control over what content to keep.

I have now failed for a full day, ending up with almost what i started with. Folders are created, but i cant data downloaded and saved into them. Hopefully someone can give me some tips/help.

Regards

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Networking;
using UnityEngine.UI;
using System.IO;

public class AssetBundleCache : MonoBehaviour
{
    
    [Header("Bundle URL")]
    public string manifestPath = "https://www";
    public string assetBundlePath = "https://www";
    [Header("Save Path")]
    public string FolderName = "";
    string modelFolderPath;
    string saveThisData;


    [Header("Loading")]
    public GameObject LoadingScreen;
    public Slider LoadingBar;
    public Text textField;

    public static AssetBundle bundle;

    [Header("Deactivate Buttons")]
    public GameObject[] ObjectsToDeactivate;
    [Header("Activate Buttons")]
    public GameObject[] ObjectsToActivate;
    [HideInInspector]
    public string[] scenePath;

    AssetBundleManifest manifest;
    UnityWebRequest request;


    List<AsyncOperation> DownloadOperation = new List<AsyncOperation>();

    // Called from button
      public void StartDownload()
    {
       
        string SavedAssetDataFolder = "SavedAssetData";
        modelFolderPath = Path.Combine(Application.dataPath, SavedAssetDataFolder).Replace('/', '\\');
        saveThisData = Path.Combine(modelFolderPath, FolderName).Replace('/', '\\');   

        if (!Directory.Exists(Path.GetDirectoryName(FolderName)))
        {
            Directory.CreateDirectory(saveThisData);
            if (StaticSettingsInfo.EnterDebugMode) { Debug.Log(FolderName + " folder Created in : " + saveThisData); }
        }
      
        CreateFolder.RefreshAssetDataFolder();

        StartCoroutine(DownloadAndCacheAssetBundle());
    }

    float totalSceneDownloadProgress;
    public IEnumerator DownloadAndCacheAssetBundle()
    {
        /*Downloadhandler*/
        /* var DH = new DownloadHandlerFile(saveThisData, false);
         DH.removeFileOnAbort = true;
         request.downloadHandler = DH;

         Debug.Log("DownloadHandler error " + DH.error); */

        Debug.Log("AssetBundle Cache and Download started");
        // Load the manifest (from url)
        UnityWebRequest www = UnityWebRequestAssetBundle.GetAssetBundle(manifestPath);
        yield return www.SendWebRequest();

        AssetBundle manifestBundle = DownloadHandlerAssetBundle.GetContent(www);
        AssetBundleManifest manifest = manifestBundle.LoadAsset<AssetBundleManifest>("AssetBundleManifest");

        // Use the default cache here
        Caching.currentCacheForWriting = Caching.defaultCache;

        string[] allAssetBundleNames = manifest.GetAllAssetBundles();
        foreach (string assetBundleName in allAssetBundleNames)
        {
            // Get hash of bundle
            Hash128 hash = manifest.GetAssetBundleHash(assetBundleName);
            Debug.Log("Hash for bundle " + assetBundleName + " is " + hash);

            // Download actual content bundle
            /*UnityWebRequest*/
            request = UnityWebRequestAssetBundle.GetAssetBundle(assetBundlePath, hash, 0);                   

            DownloadOperation.Add(request.SendWebRequest());

            for (int i = 0; i < DownloadOperation.Count; i++)
            {

                while (!DownloadOperation[i].isDone)
                {
                   
                    totalSceneDownloadProgress = 0;

                    foreach (AsyncOperation operation in DownloadOperation)
                    {
                        totalSceneDownloadProgress += operation.progress;
                    }

                    totalSceneDownloadProgress = (totalSceneDownloadProgress / DownloadOperation.Count) * 100f;                  
                    textField.text = totalSceneDownloadProgress.ToString("F0") + "%";
                    LoadingBar.value = Mathf.RoundToInt(totalSceneDownloadProgress);

                    yield return null;
                }
            }
          

            // 200 means downloaded from web and Code 0 means loaded from cache.
            Debug.Log(request.responseCode);
           
            if (request.responseCode == 0)
            {
                Debug.Log("Asset loaded from cache");              
            }

            if (request.responseCode == 200)
            {
                Debug.Log("Asset downloaded from cloud");
                LoadingScreen.gameObject.SetActive(true);
            }

            // Download the actual content bundle
            /*AssetBundle*/
            bundle = DownloadHandlerAssetBundle.GetContent(request);          
            // List all the cached versions of the given asset bundle
            List<Hash128> listOfCachedVersions = new List<Hash128>();
            Caching.GetCachedVersions(assetBundleName, listOfCachedVersions);
            Debug.Log("AssetbundleName = " + assetBundleName);

           


            for (int i = 0; i < listOfCachedVersions.Count; i++)
            {
                Debug.Log("Found cached version of " + assetBundleName + " version: " + listOfCachedVersions[i]);
            }         

            for (int i = 0; i < ObjectsToDeactivate.Length; i++)
            {
                ObjectsToDeactivate[i].SetActive(false);
            }

            for (int i = 0; i < ObjectsToActivate.Length; i++)
            {
                ObjectsToActivate[i].SetActive(true);
            }

            LoadingScreen.gameObject.SetActive(false);
            scenePath = bundle.GetAllScenePaths();          
        }

    }

    public void AbortDownload()
    {
        if (request != null && !request.isDone)
        {
            request.Abort();
            Debug.Log("Download was aborted.", this);
        }
        else
        {
            Debug.Log("Not downloading, nothing to abort...", this);
        }
    }

}

Ugh… use caution: we used to do this but you would not believe how painful such a simple UI gets to maintain. Users generally do NOT want to deal with this stuff, with the exception of a few select “power users.”

For another, when you pull down a file and write it to disk, back in the day you had to actually hold it all in memory at one point. This made us utterly run out of memory on iPad2s and smaller Android devices.

But if you’re gonna go for it, even though you’re just downloading data and writing it to disk, the same standard networking stuff applies:

Networking, UnityWebRequest, WWW, Postman, curl, WebAPI, etc:

And setting up a proxy can be very helpful too, in order to compare traffic:

Beyond that, what is often happening in these cases is one of the following:

  • the code you think is executing is not actually executing at all
  • the code is executing far EARLIER or LATER than you think
  • the code is executing far LESS OFTEN than you think
  • the code is executing far MORE OFTEN than you think
  • the code is executing on another GameObject than you think it is

To help gain more insight into your problem, I recommend liberally sprinkling Debug.Log() statements through your code to display information in realtime.

Doing this should help you answer these types of questions:

  • is this code even running? which parts are running? how often does it run? what order does it run in?
  • what are the values of the variables involved? Are they initialized? Are the values reasonable?
  • are you meeting ALL the requirements to receive callbacks such as triggers / colliders (review the documentation)

Knowing this information will help you reason about the behavior you are seeing.

You can also put in Debug.Break() to pause the Editor when certain interesting pieces of code run, and then study the scene

You could also just display various important quantities in UI Text elements to watch them change as you play the game.

If you are running a mobile device you can also view the console output. Google for how on your particular mobile target.

Here’s an example of putting in a laser-focused Debug.Log() and how that can save you a TON of time wallowing around speculating what might be going wrong:

1 Like

Thank you. We are not making a game, but an visualization application for a client. We now got 10 scenes ranging from 200 to 400 MB, and will probably end up with about 100 scenes. That’s why we need them downloaded, and give them control over data. So their costumers easily can download items that are of interest and remove those who are not relevant to them anymore.

UI is loaded and instantiated from the resource folder, and every scene buildt up the same way. But i can see why it could get troublesome when adding new functionality as we would need to rebuild every scene to cloud. But we can also make changes easily and use a persistent URL so we could add to/eidt a scene without updating the program.

The download script works, and everything runs smoothly on PC and MacBook. It also runs on Android and iOS, but encountered memory issues when downloading the biggest assetbundle scenes. With mulitiple camera options, including VR on Windows. Now i just got to figure out what to tweak in the current script to get it to save the bundle into specific folders.

I will check out those links later today. Had back to back meetings all day.

Best Regards

One thing that might help here is additively-loaded scenes, assuming your scenes can be chopped up this way.

That way you would save your scene as multiple scenes, and hence multiple asset bundles that collectively constitute one scene, and then load each one additively, unloading its asset bundle before doing the next one.

Allow me to offer my standard additive loading blurb:

Additive scene loading is one possible solution:

https://discussions.unity.com/t/820920/2
https://discussions.unity.com/t/820920/4

https://discussions.unity.com/t/824447/2

A multi-scene loader thingy:

https://pastebin.com/Vecczt5Q

My typical Scene Loader:

https://gist.github.com/kurtdekker/862da3bc22ee13aff61a7606ece6fdd3

Other notes on additive scene loading:

https://discussions.unity.com/t/805654/2

Timing of scene loading:

https://discussions.unity.com/t/813922/2

Also, if something exists only in one scene, DO NOT MAKE A PREFAB out of it. It’s a waste of time and needlessly splits your work between two files, the prefab and the scene, leading to many possible errors and edge cases.

Two similar examples of checking if everything is ready to go:

https://discussions.unity.com/t/840487/10

https://discussions.unity.com/t/851480/4

Thank you very much for tips and insight.
Unfortunately I Won’t be able to go thorough every link before Monday. Got a “beta” showroom to set up this weekend, with an 6 hour drive (+ one charge stop) from where I live.

I will update once I get back, and have a try.

Small update.

I’m getting closer to a solution. Here is the steps I go trough.

First I download the manifest file, then the script makes a folder with the name of the bundle and with downloadhandlerfile place the main bundle Into said folder. When its done, i do a bundle unload. Then it starts on the next link in the array, and same process (- create new folder). When everything is downloaded, i can press «load scene» and everything loads in nicely with additive loading. When the folder is created, it sends a «refresh folders» fuction that checks if a folder is created, and if there is a new folder, it creates a button to delete the folder. Giving the user more control over data.

Now i just got to figure out how set version, so that the script don’t download the file everytime, but only first time and if its updated. I’ll work on that next week.

Thanks for your help so far.

There’s a handy hash thing with Asset Bundles. With Addressables I think it might be implicit but I’m not sure.

Yes, i got I working with hash when doing download and cache.

Tried using the same method, adding something like what I got below. (Wrote from memory on phone).

 Hash128 hash = manifest.GetAssetBundleHash(assetBundleName);
DH = new DownloadHandlerFile (path)
DH.abort = true

request.downloadHandler = DH

request = UnityWebRequestAssetBundle.GetAssetBundle(assetBundlePath, hash, 0);

That’s the only thing I’ve tried so far, but will work some more on it next week.