Overview
Unity Addressables makes it much easier to handle remote asset downloads and cache management.
However, when you use it in a real project, you may run into issues like these:
- Calling
DownloadDependenciesAsyncdoes not make downloads as fast as expected - Downloading by label pulls in assets you did not expect
- Manually managing Address and Label settings becomes painful
This article focuses on the points that are easy to overlook when designing downloads with Addressables:
- Configuring the number of concurrent downloads
- How to think about concurrent downloads on iOS and Android
- Why you should start downloading larger files first
- Bulk downloads using labels
- The difference between labels and AssetBundle download units
- Automating management with Smart Addresser
Bundle splitting and LZ4/LZMA compression will be covered in a separate article, so this article only touches on them where necessary.
Addressables download speed is not determined by a single API call
When pre-downloading assets with Addressables, the API you will often use is:
Addressables.DownloadDependenciesAsync(label);
This is useful because it allows you to download the dependencies associated with a label or address ahead of time.
That makes the user experience more stable than suddenly downloading assets right before a loading sequence.
However, download speed is not determined just by calling this API.
In practice, it depends on factors such as:
- The number of concurrent downloads
- CDN or server response speed
- The user's network condition
- AssetBundle size
- The number of AssetBundles
- Device performance
- Decompression and loading cost after download
Especially in mobile games, the number of concurrent downloads and the order in which downloads are started are very important.
Configure the number of concurrent downloads properly
Addressables allows you to control how many web requests run at the same time.
At runtime, you can configure it with the following API:
WebRequestQueue.SetMaxConcurrentRequests(maxRequests);
If the number of concurrent downloads is too low, you may not fully utilize the available network or CDN performance.
On the other hand, if it is too high, you may run into problems such as:
- Increased load on low-end devices
- Lower frame rate during downloads
- More heat generation
- Bottlenecks during decompression or loading after downloads
- Instability depending on the network condition
In other words, more concurrent downloads do not always mean faster downloads.
You need to tune this based on your target devices, Bundle size, network environment, and loading screen design.
Use 10 on iOS, and switch between 3 and 10 on Android based on device performance
In my projects, I use the following policy based on real-device testing.
| Platform | Concurrent downloads |
|---|---|
| iOS | 10 |
| High-end Android devices | 10 |
| Low- to mid-range Android devices | 3 |
The important point is that this is not an official fixed recommendation from Unity.
It is a practical policy adopted after testing within a specific project.
:::message
In this article, I use 10 concurrent downloads on iOS and either 3 or 10 on Android depending on device performance, as a practical policy based on real-device testing.
The optimal value depends on your CDN, network condition, Bundle size, and device performance, so make sure to measure it on actual devices.
:::
iOS devices tend to have less variation in performance, so it is easier to use a relatively aggressive setting.
Android devices vary much more, so it is safer to switch the value based on device performance instead of using 10 for every device.
Configure concurrent downloads at runtime
If you want to change the number of concurrent downloads per device, call WebRequestQueue.SetMaxConcurrentRequests() when the app starts.
using UnityEngine;
using UnityEngine.ResourceManagement;
public static class AddressablesDownloadTuning
{
public static void Apply()
{
int maxRequests = GetMaxConcurrentRequests();
WebRequestQueue.SetMaxConcurrentRequests(maxRequests);
Debug.Log($"Addressables max concurrent requests: {maxRequests}");
}
private static int GetMaxConcurrentRequests()
{
#if UNITY_IOS
return 10;
#elif UNITY_ANDROID
return GetAndroidDeviceScore() >= 3 ? 10 : 3;
#else
return 10;
#endif
}
#if UNITY_ANDROID
private static int GetAndroidDeviceScore()
{
int score = 0;
if (SystemInfo.systemMemorySize >= 6000)
score += 2;
else if (SystemInfo.systemMemorySize >= 4000)
score += 1;
if (SystemInfo.processorCount >= 8)
score += 1;
if (SystemInfo.graphicsMemorySize >= 2048)
score += 1;
return score;
}
#endif
}
Call it during app initialization.
using UnityEngine;
public class AppInitializer : MonoBehaviour
{
private void Awake()
{
AddressablesDownloadTuning.Apply();
}
}
The key is to configure this before Addressables downloads begin.
It is easier to manage if you call it at a fixed timing, such as during initial app setup or around Addressables initialization.
Adjust Android scoring for your project
In the sample above, the Android device score is calculated using:
- System memory
- CPU core count
- GPU memory
However, this scoring method is only an example.
In practice, the best criteria will differ depending on your project's target devices, Bundle sizes, download-time load, and loading screen design.
For example, you may want to tune it while checking:
- Minimum target device specs
- FPS during downloads
- Memory usage during downloads
- Decompression and loading time after downloads
- Heat generation
- Network conditions
- Whether other loading tasks also run during downloads
Android devices vary widely, so you cannot simply say, "This device has a lot of memory, so 10 is fine."
Even if the memory and CPU specs look good, storage performance or network conditions can still make download behavior unstable.
For that reason, device scoring should not be treated as a fixed answer.
It should be adjusted per project based on real-device testing.
Personally, I first decide a safe value for low-end devices, then increase the number of concurrent downloads only for higher-end devices.
Start downloading larger files first when downloading multiple files
When configuring concurrent downloads, there is one more thing to consider: the order in which downloads are started.
If you manage your own queue of multiple Bundles or labels, it is generally better to start with the largest files first.
The reason is that you want to avoid leaving one huge file until the very end.
For example, suppose the maximum number of concurrent downloads is 3 and you have the following download targets.
| File | Size |
|---|---|
| A | 300MB |
| B | 20MB |
| C | 10MB |
| D | 8MB |
| E | 5MB |
If you start from the smallest files, the download may look smooth at first.
However, if the 300MB file is left until the end, the progress can appear to stall near the end of the loading screen.
Starting from smaller files
[ 5MB ] [ 8MB ] [ 10MB ]
[ 20MB ]
[ 300MB ] <- Only the largest file remains at the end
On the other hand, if you start downloading the larger file first, the 300MB file can progress in the background while smaller files are processed in the remaining request slots.
Starting from larger files
[ 300MB ] [ 20MB ] [ 10MB ]
[ 8MB ] [ 5MB ] <- Smaller files are processed while the larger one is already running
Even with the same number of concurrent downloads, the perceived waiting time can change depending on the order.
This is especially useful when you download multiple additional assets together on a loading screen.
In that case, it is usually better to enqueue larger Bundles first.
However, when you call Addressables.DownloadDependenciesAsync(label) for a single label, it is difficult to precisely control which Bundle is downloaded first internally.
If you want explicit control over the download order, split your download targets by label or key, get their sizes in advance, and then start downloads in descending order of size.
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement.AsyncOperations;
public class AddressablesPreloadQueue : MonoBehaviour
{
[SerializeField]
private List<string> preloadLabels = new()
{
"scenario_chapter_01",
"voice_jp_chapter_01",
"character_hero_001",
};
private IEnumerator Start()
{
yield return PreloadBySize(preloadLabels);
}
private IEnumerator PreloadBySize(IReadOnlyList<string> labels)
{
var targets = new List<DownloadTarget>();
foreach (string label in labels)
{
var sizeHandle = Addressables.GetDownloadSizeAsync(label);
yield return sizeHandle;
if (sizeHandle.Status == AsyncOperationStatus.Succeeded &&
sizeHandle.Result > 0)
{
targets.Add(new DownloadTarget(label, sizeHandle.Result));
}
Addressables.Release(sizeHandle);
}
targets.Sort((a, b) => b.Size.CompareTo(a.Size));
var handles = new List<AsyncOperationHandle>();
foreach (var target in targets)
{
Debug.Log($"Download start: {target.Label}, size: {target.Size} bytes");
var handle = Addressables.DownloadDependenciesAsync(target.Label, false);
handles.Add(handle);
}
while (handles.Any(x => !x.IsDone))
{
yield return null;
}
foreach (var handle in handles)
{
if (handle.Status != AsyncOperationStatus.Succeeded)
{
Debug.LogError("Download failed.");
}
Addressables.Release(handle);
}
}
private readonly struct DownloadTarget
{
public readonly string Label;
public readonly long Size;
public DownloadTarget(string label, long size)
{
Label = label;
Size = size;
}
}
}
The important point is that WebRequestQueue.SetMaxConcurrentRequests() controls the number of concurrent downloads, while your own queue controls the order in which downloads are started.
For example, if the concurrent request limit is 3, even if you start downloads in descending order of size, only up to 3 web requests will run at the same time.
In short, Addressables bulk downloads become more stable when you think about these three points together:
- Match the number of concurrent downloads to device performance
- Enqueue download targets from largest to smallest
- Design labels and Bundle splitting together
Bulk downloads using labels
Addressables allows you to use labels to handle multiple assets as a group.
For example, if you want to download all assets with the preload label, you can write:
Addressables.DownloadDependenciesAsync("preload");
However, in production, it is usually better to check the download size before starting the download.
Addressables.GetDownloadSizeAsync("preload");
If the assets are already cached, the download size will be 0.
This allows you to implement behavior such as:
- Check whether a download is required
- Show the download size to the user
- Show a Wi-Fi recommendation
- Skip the process if the assets are already downloaded
Sample code for DownloadDependenciesAsync
The following sample checks the download size for a label, then downloads it if necessary.
using System.Collections;
using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement.AsyncOperations;
public class AddressablesPreloader : MonoBehaviour
{
[SerializeField]
private string preloadLabel = "preload";
private IEnumerator Start()
{
yield return Preload(preloadLabel);
}
private IEnumerator Preload(string label)
{
var sizeHandle = Addressables.GetDownloadSizeAsync(label);
yield return sizeHandle;
if (sizeHandle.Status != AsyncOperationStatus.Succeeded)
{
Debug.LogError($"Failed to get download size: {label}");
Addressables.Release(sizeHandle);
yield break;
}
long downloadSize = sizeHandle.Result;
Addressables.Release(sizeHandle);
if (downloadSize <= 0)
{
Debug.Log($"Already downloaded: {label}");
yield break;
}
Debug.Log($"Download size: {downloadSize} bytes");
var downloadHandle = Addressables.DownloadDependenciesAsync(label, false);
while (!downloadHandle.IsDone)
{
var status = downloadHandle.GetDownloadStatus();
Debug.Log($"Download progress: {status.Percent:P0}");
yield return null;
}
if (downloadHandle.Status == AsyncOperationStatus.Succeeded)
{
Debug.Log($"Download completed: {label}");
}
else
{
Debug.LogError($"Download failed: {label}");
}
Addressables.Release(downloadHandle);
}
}
In this sample, DownloadDependenciesAsync(label, false) is used, and Addressables.Release(downloadHandle) is called manually after completion.
If you want to display progress or check the result, it is easier to manage the lifetime of the handle yourself.
How to design labels
A label should be designed as a key that represents which group of assets you want to handle together at runtime.
For example, you might use labels like these:
| Label | Purpose |
|---|---|
preload |
Minimum assets required on first launch |
scenario_chapter_01 |
Scenario text, backgrounds, and character sprites used in chapter 1 |
event_2026_summer |
Assets for a limited-time summer event |
character_hero_001 |
Assets for a specific character |
voice_jp_chapter_01 |
Japanese voice assets for chapter 1 |
The important point is to design labels based on the actual loading flow.
For example, in an ADV or visual-novel-style game, you might use groups such as:
- Common UI required on first launch
- Title screen
- Tutorial
- Scenario assets per chapter
- Backgrounds per chapter
- Character sprites per chapter
- Voice assets per language
- Limited-time events
For a game where characters are added over time, labels like these may also work well:
character_hero_001character_hero_002skin_hero_001_summervoice_hero_001_jp
Label design becomes much easier if you work backward from when the user needs each group of assets.
Labels are assigned to assets, but downloads happen per Bundle
This is an easy point to misunderstand.
Addressables labels are assigned to Addressable assets.
However, what actually gets downloaded is the AssetBundle.
:::message alert
A label is a key used to specify which group of assets should be downloaded.
However, the actual download unit is the AssetBundle.
So even if you assign a label to an asset, it does not necessarily mean only that specific asset will be downloaded in isolation.
:::
For example, suppose you have the following setup:
-
A.prefabhas thepreloadlabel -
B.prefabdoes not have thepreloadlabel - But
A.prefabandB.prefabare included in the same AssetBundle
In this case, even if you download using the preload label, the AssetBundle that contains A.prefab will be downloaded.
If B.prefab is also included in that same AssetBundle, B.prefab will effectively be downloaded as well.
In other words, labels are the entry point for selecting download targets, but the actual file unit that gets downloaded is the AssetBundle.
Design labels and Bundle splitting together
Even if your labels are finely designed, the download unit remains large if your Bundles are grouped too broadly.
On the other hand, if you split Bundles too aggressively, the number of files increases.
An increased file count can lead to issues such as:
- More requests
- More complex catalog and dependency resolution
- A large number of tiny Bundles
- Higher file lookup and management cost
For this reason, label design and Bundle splitting should always be considered together.
For example, assets like ADV or visual-novel-style scenario files often have many small files.
In that case, splitting every scenario text file, small JSON file, or small configuration file into its own Bundle can create too many files and directly increase download or loading overhead.
For this kind of data, it is often more stable in practice to group assets by chapter or event.
On the other hand, assets like character 3D models are often added over time.
For those, a design close to one Bundle per character may be more suitable.
If characters are added, updated, or removed on a per-character basis, it is easier to operate when they can be downloaded in that same unit.
I will cover this in more detail in the next article.
Manual label management is error-prone
For small projects, manually managing Addressables from the Addressables Groups window is usually fine.
However, as the number of assets grows, manual management becomes painful.
Common problems include:
- Forgetting to assign labels
- Inconsistent label naming
- Old labels remaining unintentionally
- Address naming rules becoming inconsistent
- Assets being placed in unintended Groups
- Forgetting to clean up assets after an event ends
This becomes especially problematic in live-service games, where assets increase with each event or new character.
If every Address and Label is managed manually in that situation, mistakes are almost guaranteed to happen eventually.
Addressables is powerful, but it can become hard to maintain if your operational rules are unclear.
Use Smart Addresser
If you want to manage Address and Label settings through rules, a tool like Smart Addresser can help a lot.
Smart Addresser is an Addressables support tool published by CyberAgentGameEntertainment.
It is not an official Addressables feature, but it can automate management through rules, including:
- Automatic Address assignment
- Automatic Label assignment
- Version management
- Rule validation
- CLI-based application
Manual management is enough for small projects.
However, once the number of assets grows, it is safer to generate Address and Label settings from rules instead of assigning them by hand.
In team development, it is especially important to make sure the same Address and Label settings are generated no matter who works on the assets.
Recommended practical setup
In practice, the following setup is easy to operate.
Configure concurrent downloads at startup
Call WebRequestQueue.SetMaxConcurrentRequests() when the app starts.
Use a fixed value for iOS, and switch based on device performance for Android, depending on your project.
Assign the preload label only to the minimum required assets
Include only the assets required for first launch or the title screen in preload.
If you put too many assets into this label, the initial download becomes heavy.
It is better to keep preload limited to assets that are absolutely required at the beginning.
Split labels by chapter, event, or character
Design labels based on the loading flow.
scenario_chapter_01
scenario_chapter_02
event_2026_summer
character_hero_001
voice_jp_chapter_01
Labels are easier to manage when they match the actual game progression.
Check the download size before downloading
Before a bulk download, use GetDownloadSizeAsync() to check the size.
If the size is large, you can show a confirmation dialog or recommend Wi-Fi.
If the assets are already cached, the size will be 0, so you can skip unnecessary download processing.
Start larger downloads first when downloading multiple targets
If you manage your own download queue for multiple labels or Bundles, start from the largest one.
This helps avoid the situation where only a huge Bundle remains at the end, reducing the feeling that progress has stalled near the end of the loading screen.
Design labels and Bundle splitting together
Even if your labels are cleanly separated, the actual download unit stays large if the Bundle is large.
On the other hand, splitting Bundles too finely increases the number of files.
That is why labels and Bundle splitting should be designed together.
Summary
When designing downloads with Addressables, the number of concurrent downloads and label design are important.
Simply calling DownloadDependenciesAsync does not determine download speed or operational stability.
The number of concurrent downloads should not be increased blindly.
It needs to be tuned based on device performance, network conditions, and Bundle size.
In my projects, I use 10 on iOS and either 3 or 10 on Android depending on device performance, based on real-device testing.
Also, when you manage your own queue for multiple download targets, starting from larger Bundles first helps avoid leaving one huge file until the end.
Labels make bulk downloads easy.
However, labels are only keys used to select download targets.
The actual download unit is the AssetBundle.
For that reason, label design and Bundle splitting should be considered together.
As the number of assets grows, manual Address and Label management becomes more error-prone.
In that case, using a rule-based management tool such as Smart Addresser can make operations much easier.
Next: Bundle splitting and LZ4/LZMA
In the next article, I will cover Addressables Bundle splitting and compression settings.
In particular, I plan to cover topics such as:
- Whether many small files, such as ADV scenario files, should be grouped together
- Whether character 3D models that will increase over time should use one Bundle per character
- Download and lookup overhead caused by increasing the number of files
- The downsides of splitting Bundles too finely
- How to choose between LZ4 and LZMA
Bundle splitting is one of the areas where practical Addressables operation differs greatly between projects.
Next time, I will organize more concrete decision criteria.
Top comments (0)