Based on my previous thread I succeeded to load external content into my application. But now I am facing the problem to load the content from the local device when the content has been downloaded once before.
My solution is similar to the solution from this post and works like this:
InitializeAsync()
LoadContentCatalogAsync()
LoadResourceLocationsAsync()
DownloadDependenciesAsync()
LoadAssetsAsync()
LoadSceneAsync()
As said everything works fine this way. But I don’t want the application to download the content on every start. Instead I expect it to download it once and check on every restart whether the content does already exist on the local device (as I thought that that’s the use case of addressables).
So how can I load the content to the local device? DownloadDependenciesAsync() sounds like this is already done but I can’t find a path where the content might be saved.
I thought maybe I do not even have to care about this and tried to skip the download part, so executing just step 1 and jump from there directly to step 5. But I just recieve an InvalidKeyException (even though the key was correct before).
Depending on your target platform, Addressables uses the caching mechanism of that platform, if available. So the next time you load any assets, it will pull from cache if it exists, or it will fallback to download. Obviously, if the platform doesn’t support caching, it will download every time. This can be disabled in group’s advanced settings.
If the platform’s cache isn’t good enough for you, and you want to download to an absolute path on the machine, I think you will have to handle that on your own. That can be achieved by doing a regular download of your content (not using Addressables API), then initialize Addressables with a path that points to that downloaded path.
Your words sound like if it’s enough to start with Addressables.LoadAssetAsync once the content has been downloaded before. But this doesn’t work for me.
You mean GroupSettings → Content Packing & Loading → Advanced Options → Use Asset Bundle Cache? This option is turned on.
My function for loading addressables looks like this (Addressables.InitializeAsync() has been called before!):
/// <summary>
/// Loads the addressable asset asynchronus
/// </summary>
/// <param name="name">Name of the asset</param>
/// <param name="type">Type of the asset</param>
private IEnumerator LoadAddressableAssetAsync(string name, ContentType type, bool isOfflineAvailable)
{
if(!isOfflineAvailable)
{
// Load content from catalog and wait for it
AsyncOperationHandle<IResourceLocator> handleCatalog = Addressables.LoadContentCatalogAsync(catalogPath);
while (!handleCatalog.IsDone)
{
yield return null;
}
// Load location of resources and wait for it
AsyncOperationHandle<IList<IResourceLocation>> handleResource = Addressables.LoadResourceLocationsAsync("");
while (!handleResource.IsDone)
{
yield return null;
}
// Save locations
List<IResourceLocation> locations = new List<IResourceLocation>(handleResource.Result);
// Download dependencies and wait for it
AsyncOperationHandle handleDependencies = Addressables.DownloadDependenciesAsync(name);
while (!handleDependencies.IsDone)
{
yield return null;
}
}
// Load the given asset depending on it's type
if (type == ContentType.SCENE)
{
// Load desired scene and wait for it
AsyncOperationHandle<Scene> handleAsset = Addressables.LoadAssetAsync<Scene>(name);
while (!handleAsset.IsDone)
{
yield return null;
}
}
else if (type == ContentType.GAMEOBJECT)
{
// Load desired gameobject and wait for it
AsyncOperationHandle<GameObject> handleAsset = Addressables.LoadAssetAsync<GameObject>(name);
while (!handleAsset.IsDone)
{
yield return null;
}
}
yield return null;
}
What doesn’t work? I believe LoadContentCatalogAsync will always download in the current version of Addressables. I never use LoadResourceLocationsAsync and I’m not sure how it works. But why are you even loading that and not using it? I also never use DownloadDependenciesAsync and am not sure how it works.
In my code, I only Initialize (always downloads the catalog) then LoadAsset or LoadScene (always pulls it from the cache).
Good point. I don’t know what it does either. That’s the copy&pasted part and I didn’t realize that I don’t use it. So I just removed it.
So I’ve got this function now:
/// <summary>
/// Loads the addressable asset asynchronus
/// </summary>
/// <param name="name">Name of the asset</param>
/// <param name="type">Type of the asset</param>
private IEnumerator LoadAddressableAssetAsync(string name, ContentType type, bool isOfflineAvailable)
{
// Download asset from server if they are not locally available
if(!isOfflineAvailable)
{
// Load content from catalog and wait for it
AsyncOperationHandle<IResourceLocator> handleCatalog = Addressables.LoadContentCatalogAsync(catalogPath);
while (!handleCatalog.IsDone)
{
yield return null;
}
// Download dependencies and wait for it
AsyncOperationHandle handleDependencies = Addressables.DownloadDependenciesAsync(name);
while (!handleDependencies.IsDone)
{
yield return null;
}
}
// Load the given asset depending on it's type
if (type == ContentType.SCENE)
{
// Load desired scene and wait for it
AsyncOperationHandle<Scene> handleAsset = Addressables.LoadAssetAsync<Scene>(name);
while (!handleAsset.IsDone)
{
yield return null;
}
}
else if (type == ContentType.GAMEOBJECT)
{
// Load desired gameobject and wait for it
AsyncOperationHandle<GameObject> handleAsset = Addressables.LoadAssetAsync<GameObject>(name);
while (!handleAsset.IsDone)
{
yield return null;
}
}
yield return null;
}
Adressables are initialized in the start function and a scene is loaded within another function:
/// <summary>
/// Loads the given scene
/// </summary>
/// <param name="name">Name of the scene to load</param>
private void LoadScene(string name)
{
Addressables.LoadSceneAsync(name);
}
I expect the following behaviour:
When I start the program for the first time I want to load some asset (e.g. a scene) from the remote path and load it. For this purpose I call LoadAddressableAssetAsync(sceneName, ContentType.SCENE, isOfflineAvailable=false). Now the scene asset should be loaded from the remote path so that I can load the scene with LoadScene(sceneName). The functions are called in this order:
InitializeAsync()
LoadContentCatalogAsync()
DownloadDependenciesAsync()
LoadAssetsAsync()
LoadSceneAsync()
This part works fine!
As the next step I shutdown the program and restart it. I do now expect the addressable assets to be stored in some cache since they should have been downloaded in the first run. So this time I call LoadAddressableAssetAsync(sceneName, ContentType.SCENE, isOfflineAvailable=true) and load the scene again with LoadScene(sceneName). The functions are called in this order:
Notice the difference between these 2? I don’t think you can load a Scene through LoadAsset function. Is this not blowing up the first time you try it?
I got this to work and only have to download it once , I have 2 buttons one runs downloadsizeasync() to check if I need to download then I run downloaddependencies with a progress bar , then the other button runs loadresourceasync and loadlevelasync , then I close the android app and run it again , this time download button prints already downloaded , and load button load the scene , even after closing the app size is increased.
Now I must warn you that I dont know if this is the proper way to do it , nonetheless it works for me , with my scene assetsbundle uploaded on AWS S3 bucket , once you select build script you can even delete the scene from editor and it will work . Mind you the progress bar is giving me issues. Once download button gives a Resource collected text you can press Load button. This will stay in your apk cache and not download again . If some one can point us to how its done correctly or just by using URL which im struggling to do please reply back asap.
So I have two buttons :
1- Download button
2- Load Button
You can get out of the function after it prints Already Downloaded , I was just testing stuff. If you want you can check the app size before and after download so yeah it only downloads once.
So turns out it never works for me if I dont run download resource location first , it will also work if you use handleResource.Result[0] if its a single scene.Resource download does not change the app size I have checked 5 times . Strange.
Now remember the scenes loaded from addressables cannot be used with scenemanager , so when you get the result back store it in a generic or create an IResourceLocation list to save it for later or create a Label List , and when you change the level no need to unloadAsync .
Sadly not. Your code doesn’t work in my case (catalog from URL). I have two buttons as well, “Download scene” and “Load scene”. When I click Download first and Load second everything works fine. But after restarting the program and clicking just Load I just recieve an error at LoadSceneAsync():
Exception encountered in operation UnityEngine.ResourceManagement.ResourceManager+CompletedOperation`1[UnityEngine.ResourceManagement.ResourceProviders.SceneInstance], result=‘’, status=‘Failed’: Exception of type ‘UnityEngine.AddressableAssets.InvalidKeyException’ was thrown., Key=Scene3, Type=UnityEngine.ResourceManagement.ResourceProviders.SceneInstance UnityEngine.AddressableAssets.Addressables:LoadSceneAsync(Object, LoadSceneMode, Boolean, Int32)
Here is the code for each of these:
Download Scene:
private IEnumerator DownloadScene(string name)
{
// Load content from catalog and wait for it
AsyncOperationHandle<IResourceLocator> handleCatalog = Addressables.LoadContentCatalogAsync(catalogPath);
while (!handleCatalog.IsDone)
{
yield return null;
}
// Download dependencies and wait for it
AsyncOperationHandle handleDependencies = Addressables.DownloadDependenciesAsync(name);
while (!handleDependencies.IsDone)
{
yield return null;
}
yield return null;
}
Load Scene:
private IEnumerator LoadScene(string name)
{
// Load resource locations and wait for it
AsyncOperationHandle<IList<IResourceLocation>> handleResource = Addressables.LoadResourceLocationsAsync(name);
while (!handleResource.IsDone)
{
yield return null;
}
// Load scene async and wait for it
AsyncOperationHandle<SceneInstance> handleCatalog = Addressables.LoadSceneAsync(name);
while (!handleCatalog.IsDone)
{
yield return null;
}
yield return 0;
}
I can’t see any difference to your code but it’s still not working?! I am using Adressable Package 1.6 with unity 2019.3 as well. But I am building for Windows Standalone. Is this maybe a problem? Or do I need some special settings for this? Maybe I should also try to build for Android just to make sure that it’s not a plattform problem.
As said the catalog was builded in another unity project and I download the catalog from AWS S3 bucket just as you do. Did you also build your catalog in another unity project? Or have you some special settings?
It throwed indeed an error message, thanks for this! At least this error is now gone.
From my testing, I believe Addressables.LoadContentCatalogAsync(catalogPath) does both download AND load content catalog much like other apis in Addressables like LoadAssetsAsync(). When you call LoadContentCatalogAsync(catalogPath), part of api loads catalog you explicitly specify on top of your built in one.
You might have confused its functionality with it being replacing/merging your original built in catalog catalog at with one at “catalogPath”, but it doesn’t seems to work like that.
So those catalogs are treated as separate catalog by Addressables, when you restart and not call LoadContentCatalogAsync again, Addressables won’t register those from “catalogPath”, hence InvalidKey error.
As with your example:
InitializeAsync() ← Loads BuiltInCatalog(lets call it CatalogA)
LoadContentCatalogAsync() ← Loads Other catalog (call it CatalogB)
Addressables seems to merge CatalogA&CatalogB for time being
DownloadDependenciesAsync()
LoadAssetsAsync() ← Can load files from CatalogA&B
LoadAssetsAsync() ← Can load files from CatalogA (because CatalogB is not loaded)
LoadSceneAsync()
I haven’t looked into inner working of that API to be sure, but from how Addressables is built, may be it can treat the additional catalog loaded by “LoadContentCatalogAsync” much the same way as other Assets, meaning it cache the whatever catalog it loads and as long as URL is the same it shouldn’t re-download it again. You can check server log if Addressables is making requests only first time or not. Though, one concern is, catalog files are not AssetBundle so it might behave differently. You may try downloading the catalog outside of the Addressables and load them with “file://local/catalogPath”. UnityWebRequest should support that local file loading with file:// url.
Yes catalogPath is a URL string. Yes thanks for helping! It would be awesome if we could solve the problem. I also keep trying.
Thanks for the insights, they could help as well! I will try it as suggested with a seperate download outside Addressables and load the catalog from there.
Interesting, this describes perfectly my problem right now with Addressable catalogues.
While I can download the remote catalogue, it is not stored/cached and with each restart of the app, I have to download again the catalogue or else I would only have the build in catalogue.
My approach will now be to download the catalogue and store it somehow on the disk, so I can load that catalogue even without internet connection. What do you guys think of it?
How are you downloading the catalog? If you’re doing it through addressables, it should be storing it in the device’s cache. If you’re downloading it manually, then yeah, that’s how it should be done. (If you do it through UnityWebRequest, it should also store it in the device’s cache, which Addressables uses under the hood.)