so currently I am trying to make all my AssetBundles downloadable from a server.
Lets say, an AssetBundle ‘assetBundle’ contains 1 single Asset ‘assetA’.
Unity3d will generate following files:
assetBundle
assetBundle.manifest
So when I try to download the AssetBundle with UnityWebRequest it works just fine, but once an AssetBundle is tried to be downloaded a second time, an error occurs “AssetBundle with that name is already loaded”.
Currently, I only download the file ‘assetBundle’ from my WebServer.
So UnityWebRequest caches the downloaded AssetBundle. I read the docs and some posts and it was said, I could use the CRC inside the .manifest-file to see whether a Bundle was already loaded or not.
So I tried downloading the .manifest-file from my WebServer aswell, but it just does not work. The docs do not have an entry about downloading .manifest-files from a WebServer and I couldn’t find anything useful with google aswell and I think thats pretty weird.
So I figured out, that I shouldn’t use the ‘.manifest’ file but the AssetBundle-File itself. Sadly when trying to load the Manifest through the code above, it says its null…
So… I changed my code to build the assets and used the StreamingAssets-Folder, before, I used to build the files outside of the unity-project-structure, there it only built 2 files. Bundle + Bundle.manifest.
Now that I built into the unity-project, it also generated a file “StreamingAssets” that was successfully loaded as a manifest.
But what should I do when I have 1000 different AssetBundles and I dont want only a single manifest-file with 1000 entries of AssetBundles?
AssetBundle with that name is already loaded means you’re loading the same bundle (or a bundle with the same name) in the same app session twice. Once you’ve loaded it the first time, you should store a reference to it and use that instead of calling GetAssetbundle again.
That, or you can unload the bundle after you’re done loading assets from it:
So there are three types of files when using Unity’s built in assetbundle build system.
The assetbundles themselve, stored as the name you gave them without extension.
A manifest file stored as .manifest
And an extra assetbundle containing exactly 1 asset of the type AssetbundleManifest, stored in the same output folder and named after the folder its stored in
The files ending with .manifest are used by the editor for incremental builds, and serve no purpose at runtime. You should not upload them to your server or download / load them in your app.
The assetbundle manifest is an important one, as it allows you to query all available bundles and gather dependencies. You don’t necesarily need this file though, if it doesn’t make sense for your usecase to store all bundles in 1 file, then hook into the build system and store the info you need in a way that makes more sense to you.
Also, instead of using the legacy API’s as I think you’re doing now, you could look into building bundles via the Scriptable Build Pipeline, which is the way forward.
You can try it… It is working for me.
Make sure to check you have permission to download the asset you want from the storage you are using (Your server / Firebase storage / Google storage / AWS S3 / Drive)
//My asset url
string url = "blablabla.com/myassets/gun1"
public void Start
{
StartCoroutine(DownloadAsset());
}
IEnumerator DownloadAsset()
{
using (UnityWebRequest uwr = UnityWebRequestAssetBundle.GetAssetBundle(url))
{
yield return uwr.SendWebRequest();
if (uwr.isNetworkError || uwr.isHttpError)
{
Debug.Log(uwr.error);
}
else
{
// Get downloaded asset bundle
AssetBundle bundle = DownloadHandlerAssetBundle.GetContent(uwr);
var prefab = bundle.LoadAsset(partName);
Instantiate(prefab, new Vector3(0, 0, 0), Quaternion.identity) as GameObject;
}
}
}
Hello @nilsdr ! Thanks a lot for the info! This is very useful!
In my case, I have to load each asset on demand. When the user clicks on a product I have to download the asset bundle and cache it. So I have to use the manifest to get the Hash and CRC for the bundle. Do you know how to build a manifest for a single bundle?
If I build 5 bundles I want to create a manifest for each bundle and upload both the bundle and the manifest to the server. When the user clicks on the product I firstly will send an HTTP HEAD to check the Hash and CRC thanks to that info I will be able to check if that bundle is cached. Then if it is not cached I will use UnityWebRequest to get the bundle and load it. Anyway, I guess if I get the manifest info the UWR built-in function should do the job… I just need to know how to get that hash and CRC values for each bundle…
Assuming your bundles are generated in one go in the same command, the resulting assetbundlemanifest will contain all hashes for all bundles. You wouldn’t need to have multiple manifests.
Currently, I have a main unity project that will hold the logic of my app and a secondary unity project just for building AssetBundles. The main unity project is communicating with a CMS(Content Management System) to download information and resources about a specific product (3D Model).
On the CMS side, you can manage the products. Like name, description, image etc
And in the same product, I can upload the bundle for IOS, Android, and Windows…
At runtime, the app will connect to the CMS, and when the user clicks on the category I will make an API call to get the product list (a basic JSON) with all this information and the path of the bundles pointing to S3.
Everything works fine but at the moment I’m not caching anything… Each time I download the bundle (3 - 5 MB) and it’s not that good…
This is exactly the stuff I don’t understand. I know when I create a build the StandaloneWindows64 is the manifest. But I can’t have all the info in one single file because the products will grow…
So my problems at the moment are:
How do I cache the downloaded bundle? I need the Hash and CRC
When I update the product on CMS the Hash and CRC will change and the app will automatically download a new bundle this is ok but how do I delete the old cache? Will the UWR replace the old cache?
I hope now that I explained the full architecture is more clear! What are your thoughts on it?
I also tried with Addressable Package but it’s not compatible with the current system so I have to handle Bundles manually and it’s really confusing!
Thats very clear, if you can change the entity/model in your CMS your best approach might be to skip the assestbundlemanifest altogether and store the hashes for each given bundle there.
After building the assetbundles for each given platform, the resulting object (the assetbundlemanifest) will contain the hash for each bundle. Add a field for each platform your CMS supports and store the hash in there.
Then, from a client side perspective, pass that hash as a parameter to the uwr when downloading the assetbundle. This will skip the download altogether if a bundle with the given hash was previously downloaded.
Yes, It makes sense! That is what I wanted to do in the first place I just needed to know if I was doing the right thing!
So to recap:
Build asset bundle
Foreach platform GET the manifest (Android, IOS, etc…)
Extract the Hash of the bundle and the CRCs. I could use these functions:
// GET HASH
Hash128 hash = manifest.GetAssetBundleHash("bundleName");
// GET CRC
BuildPipeline.GetCRCForAssetBundle(targetPath, out crc)
Upload everything to the Entity on the server
The client will load the bundle for the first time:
Call API to get the product entity (this will not be cached because its very light)
I will have access to CRC and Hash to check the cache with Networking.UnityWebRequest GetAssetBundle(string uri, Hash128 hash, uint crc)
From here everything is crisp and clear!
Now I have to figure out how to patch the bundle If someone updated it on the CMS:
From the DOC: Unity - Manual: Patching with AssetBundles
The more difficult problem to solve in the patching system is detecting which AssetBundles to replace. A patching system requires two lists of information:
A list of the currently downloaded AssetBundles, and their versioning information
A list of the AssetBundles on the server, and their versioning information
Anyway thanks a lot for the feedback! I hope this thread will be helpful :). If somebody wants to use asset bundles…
That’s the right approach. From a client side perspective you wouldn’t care if the bundle was previously cached, UWR will handle that logic.
That api will remove older bundles, you can execute it after a UWR finishes for a given bundle and hash.
A mature pipeline could query your CMS api for the latest known bundle version in a post assetbundle-build step, and then let you know your local version is newer (or even automate the upload aswel)
I just tested a really rough implementation of the workflow above.
public static async Task<AssetBundle> GetAssetBundle(string uri)
{
const string manifestPath = "http://localhost/wewear-app/StandaloneWindows64/StandaloneWindows64";
using var manifestRequestRequest = UnityWebRequestAssetBundle.GetAssetBundle(manifestPath);
var operationManifest = manifestRequestRequest.SendWebRequest();
while (!operationManifest.isDone)
await Task.Yield();
var assetBundleManifest = DownloadHandlerAssetBundle.GetContent(manifestRequestRequest);
var manifest = assetBundleManifest.LoadAsset<AssetBundleManifest>("AssetBundleManifest");
var hash = manifest.GetAssetBundleHash("cube.assetbundle");
const int crc = 465257593; // copied manually from cube.manifest
assetBundleManifest.Unload(true);
Debug.Log("HASH: " + hash);
Debug.Log("CRC: " + crc);
using var unityWebRequest = UnityWebRequestAssetBundle.GetAssetBundle(uri, hash, crc);
var operation = unityWebRequest.SendWebRequest();
while (!operation.isDone)
await Task.Yield();
return DownloadHandlerAssetBundle.GetContent(unityWebRequest);
}
It works perfectly the BUT uwr is just creating a new cache if the Hash changes. So I need a way to delete the old cache and replace it with the new bundle.