On-demand resources is a new iOS feature available since iOS and tvOS version 9.0. Its purpose is to reduce the size of the main application bundle, so that developers can separate certain resources from the main application bundle, host them on App Store infrastructure and download them on-demand.

This is very important on the new AppleTV platform where the main application bundle is limited to 200MB. Therefore, most developers will need to use dynamically loaded resources one way or another. To make developer lives easier, Unity provides an on-demand resources API wrapper which has been shipped in Unity 5.2.0 patch 1.

You can use on-demand resources to both reduce initial application download sizes and reduce the device storage usage by removing no longer needed assets. Generally, any resource that is not strictly needed to launch an app is a candidate for being loaded or unloaded on-demand. For example, consider a level-based game: the application does not need level 10 when the user is still playing level 3. On the other hand, the first levels may be safely unloaded when the user plays level 16.

The most convenient way of taking advantage of on-demand resources is via asset bundles. Asset bundles solve many remaining problems in dynamic asset loading, such as memory and CPU efficiency, dependency tracking and optimization for target platform. Once asset bundles are configured, you need very few additional changes to use on-demand resources, as shown in the code examples below. In this blog post we won’t cover asset bundles. If this is the first time you hear about them, this resource will help you.

In order to use on-demand resources, the developer needs to perform two actions: assign some identifier, called tag, to each resource during build process and request the resources using the assigned tag during app runtime when needed. In vanilla iOS app development, the first step is done by assigning tags to resources in Xcode, whereas resources are requested using NSBundleResourceRequest API. In Unity, both tag assignment and resource retrieval are performed via code: the former via UnityEditor.iOS.BuildPipeline.collectResources event API, and the latter via UnityEngine.iOS.OnDemandResources.PreloadAsync API.

While the current on-demand resources API does not constrain tag names in any way, there are several guidelines that simplify development. It’s best to assign each asset bundle a unique tag which is derived from asset bundle name. This offers most flexibility and it’s also the simplest approach. In this case, the developer will be able to set priority of each individual download and also retrieve progress of each of them. Also, remember that Apple recommends that all the resources which are assigned the same tag have a cumulative size no larger than 64MB for a good balance between download speed and storage space availability.

The following two code examples demonstrate the essence of using on demand resources:

Editor script to assign identifiers to resources:

using UnityEditor.iOS; #if ENABLE_IOS_ON_DEMAND_RESOURCES public class BuildResources { [InitializeOnLoadMethod] static void SetupResourcesBuild() { UnityEditor.iOS.BuildPipeline.collectResources += CollectResources; } static UnityEditor.iOS.Resource[] CollectResources() { return new Resource[] { new Resource("asset-bundle-name", "path/to/asset-bundle").AddOnDemandResourceTags("asset-bundle-name-tag"), }; } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 using UnityEditor . iOS ; #if ENABLE_IOS_ON_DEMAND_RESOURCES public class BuildResources { [ InitializeOnLoadMethod ] static void SetupResourcesBuild ( ) { UnityEditor . iOS . BuildPipeline . collectResources += CollectResources ; } static UnityEditor . iOS . Resource [ ] CollectResources ( ) { return new Resource [ ] { new Resource ( "asset-bundle-name" , "path/to/asset-bundle" ) . AddOnDemandResourceTags ( "asset-bundle-name-tag" ) , } ; } }

Resource retrieval during runtime:

using UnityEngine.iOS; // We can execute the following function as a coroutine, like this: // StartCoroutine(LoadAsset("asset-bundle-name", "asset-bundle-name-tag")); public static IEnumerator LoadAsset(string resourceName, string odrTag) { // Create the request var request = OnDemandResources.PreloadAsync(new string[] { odrTag } ); // Wait until request is completed yield return request; // Check for errors if (request.error != null) throw new Exception("ODR request failed: " + request.error); // Now we can use the resource, for example, by loading an asset bundle like this: // var bundle = AssetBundle.CreateFromFile("res://" + resourceName); // ... // We need to call Dispose() when resource is no longer needed. // request.Dispose(); } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 using UnityEngine . iOS ; // We can execute the following function as a coroutine, like this: // StartCoroutine(LoadAsset("asset-bundle-name", "asset-bundle-name-tag")); public static IEnumerator LoadAsset ( string resourceName , string odrTag ) { // Create the request var request = OnDemandResources . PreloadAsync ( new string [ ] { odrTag } ) ; // Wait until request is completed yield return request ; // Check for errors if ( request . error != null ) throw new Exception ( "ODR request failed: " + request . error ) ; // Now we can use the resource, for example, by loading an asset bundle like this: // var bundle = AssetBundle.CreateFromFile("res://" + resourceName); // ... // We need to call Dispose() when resource is no longer needed. // request.Dispose(); }

The easiest way to start exploring asset bundles and on-demand resources is to use our Asset Bundle Manager demo project, which is available on BitBucket. The landing page offers a comprehensible description of how to use and tweak the demo.

Several Unity games utilizing on-demand resources have been already shipped on Apple TV. These include Breakneck and Bruce Lee: Enter the Game – Unchained Edition.