Search Unity

IOS App slicing for bundles made by Addressable assets

Discussion in 'Asset Bundles' started by Kaven01, Apr 29, 2019.

  1. Kaven01

    Kaven01

    Joined:
    Nov 22, 2017
    Posts:
    18
    So I am trying to use addressable assets, but use bundles to create variants used for app slicing on IOS. As I understand it, addressables create asset bundles on their own; the only thing I have to do is to create varianted resources for XCode during build time. I have the following code in Editor folder:

    Code (CSharp):
    1. public class BuildResources
    2. {
    3.    
    4.     [InitializeOnLoadMethod]
    5.      static void SetupResourcesBuild()
    6.      {
    7.            UnityEditor.iOS.BuildPipeline.collectResources += CollectResources;
    8.      }
    9.  
    10.      static UnityEditor.iOS.Resource[] CollectResources()
    11.      {
    12.  
    13.             return new Resource[] {
    14.                     new Resource("asset-bundle-myown").BindVariant("Library/com.unity.addressables/StreamingAssetsCopy/aa/iOS/iOS/hd_assets_all_cbb822b3e4638d2d575c149e4b9de2f7.bundle", "hd")
    15.                                                     .BindVariant("Library/com.unity.addressables/StreamingAssetsCopy/aa/iOS/iOS/sd_assets_all_34f9852a59ba1c35ad5e441958549058.bundle", "sd")
    16.             };
    17.       }
    18.    
    19. }
    According to how I understand it, it should create "asset-bundle-myown" resource in XCode, with hd and sd variants made from specified asset bundles. Asset bundles are already built by Addressable assets > Build > Build Player Content. I have checked that they have exactly these file names, and that I have correct paths (relative to root folder of a project). However when building the game, it always ends with fail and folowing error:

    UnityEditor.BuildPlayerWindow+BuildMethodException: 2 errors
    at UnityEditor.BuildPlayerWindow+DefaultBuildMethods.BuildPlayer (UnityEditor.BuildPlayerOptions options) [0x00234] in /Users/builduser/buildslave/unity/build/Editor/Mono/BuildPlayerWindowBuildMethods.cs:190
    at UnityEditor.BuildPlayerWindow.CallBuildMethods (System.Boolean askForBuildLocation, UnityEditor.BuildOptions defaultBuildOptions) [0x0007f] in /Users/builduser/buildslave/unity/build/Editor/Mono/BuildPlayerWindowBuildMethods.cs:96
    UnityEditor.BuildPlayerWindow:BuildPlayerAndRun()

    I am sure that problem is in my BuildResources class, because when I comment it out, the build does not fail. However error message says nothing useful and I am missing any documentation for UnityEditor.iOS.Resource, so I don't know where is the problem. Can anyone help me?
     
  2. Kaven01

    Kaven01

    Joined:
    Nov 22, 2017
    Posts:
    18
    Okay, so I tried different things, and found out that I have to use objects of iOSDeviceRequirement class as the second parameter to BindVariant(). If I pass a string ("hd" or "sd" in my case), it is probably a reference to Variant name defined in Player preferences > Other > Variant map for app slicing . Since I don't have bundle variants defined (because I am using addressables, that don't cooperate with this settings), I could not define the map. With the following code the build finishes successfully:
    Code (CSharp):
    1. static UnityEditor.iOS.Resource[] CollectResources()
    2.      {
    3.             var iPadRequirement = new iOSDeviceRequirement();
    4.             iPadRequirement.values.Add("idiom", "ipad");
    5.            
    6.             var iPhoneRequirement = new iOSDeviceRequirement();
    7.             iPhoneRequirement.values.Add("idiom", "iphone");
    8.            
    9.             return new Resource[] {
    10.                     new Resource("testresource").BindVariant("/ProgramsUnity/TestAddressables/Library/com.unity.addressables/StreamingAssetsCopy/aa/iOS/iOS/hd_assets_all_3c5c65e46220b76e7c7a118a79c5e473.bundle", iPadRequirement)
    11.                                                     .BindVariant("/ProgramsUnity/TestAddressables/Library/com.unity.addressables/StreamingAssetsCopy/aa/iOS/iOS/sd_assets_all_64e55aac3b546328ded8044d118e9493.bundle", iPhoneRequirement)
    12.             };
    13.       }
    But I don't know what does it really do. In XCode project, I don't see any "testresource" or any variants of image contained in bundles. What does that collectResources event really do?
     
  3. Kaven01

    Kaven01

    Joined:
    Nov 22, 2017
    Posts:
    18
    OK, I have it working in the end. Result is sort of combination between old and new approach to asset bundles, but it works.
    1) I decide what assets should go to app-slicing. Let's say we want 2 slicing variants - for iphone and ipad. So I create iphone and ipad folders, put corresponding asset variants there, and make them all Addressable. Their addresses don't matter, I use addressables only to make asset bundles, but not to read them. Paths (not necessarilly addresses) should be the same, except for variant folder name (iphone or ipad).
    2) I create 2 addressable groups, iphone and ipad. I assign assets from 1) to corresponding groups. Each group has BuildPath set to RemoteBuildPath, which is set to Datasets/iOS (I created those directories in project manually in the project root). Load Path does not matter, because I will not use it.
    3) I build the addressable groups, which will create asset bundles in Datasets/iOS directory.
    4) I have editor script to include these asset bundles in XCode project in a similar way as it was used before addressables:
    Code (CSharp):
    1. public class BuildResources
    2. {
    3.     [InitializeOnLoadMethod]
    4.     static void SetupResourcesBuild()
    5.     {
    6.         UnityEditor.iOS.BuildPipeline.collectResources += CollectResources;
    7.     }
    8.  
    9.     static UnityEditor.iOS.Resource[] CollectResources()
    10.     {
    11.         var iphoneRequirement = new iOSDeviceRequirement();
    12.         iphoneRequirement.values.Add("idiom", "iphone");
    13.    var ipadRequirement = new iOSDeviceRequirement();
    14.         ipadRequirement.values.Add("idiom", "ipad");
    15.  
    16.         return new Resource[] {
    17.                 new Resource("appdata")
    18.                         .BindVariant("Datasets/iOS/iphone_assets_all.bundle", iphoneRequirement)
    19.                         .BindVariant("Datasets/iOS/ipad_assets_all.bundle", ipadRequirement)
    20.  
    21.             };
    22.     }
    23. }
    Now if I build the game, XCode project will contain resource "appdata" with variants for iphone and ipad. Now how to use data from it in game ... I do it totally without addressables.

    When game starts, I do
    1)
    var bundle = AssetBundle.LoadFromFile("res://appdata"); //this loads the bundle

    2) I detect if I am on ipad or iphone. It can be done by Unity classes, or I read one name of asset from the loaded bundle and parse out the bundle folder name:
    Code (CSharp):
    1. var assets = bundle.GetAllAssetNames();
    2. var aname = assets[0];
    3. var parts = aname.Split('/');
    4. //folder name will be somewhere in parts
    3) I can load any asset by bundle.LoadAssetAsync(path); , where i construct path out of known path of asset and variant name acquired in 2)

    In order to make game playable in editor, I make previous code #if UNITY_IOS, and create separate #if UNITY_EDITOR area. Which contains
    Code (CSharp):
    1. var path = Application.dataPath + "/../Datasets";
    2. var loadname = "..."; ///construct asset bundle file base name for required variant. eg. "ipad_assets_all.bundle"
    3. path = path + "/iOS/" + loadname;
    4. var bundle = AssetBundle.LoadFromFile(path);
     
  4. MythicLionMan49

    MythicLionMan49

    Joined:
    Apr 8, 2021
    Posts:
    10
    I got Slicing working using Addressables. This solution allows resources to be referenced by address, so the benefits of Addressables can be realized. Since part of the solution was inspired by Kaven01's approach, and because this thread kept popping up whenever I did a search on this topic I'm including a link to my solution. I forked a version of Addressables that allows multiple catalogs to be generated with extensions to support slicing. Each variant is stored in a different catalog. There is documentation on Slicing in the Readme, but here's a brief overview:

    1. I define several 'variant groups'. Variant groups are groups of assets that will be sliced differently. For instance, I might slice textures based on one criteria, and video based on another, so I have variant groups for each.
    2. Each variant in each group is defined by a different External Asset Catalog. (The naming is confusing here. In Unity an Asset Catalog is the index that defines how Addressables should look up assets. On iOS an Asset Catalog is the generated XCasset file that the App Store will slice during thinning). I create the ExtraCatalog assets, assign the Addressable groups to them, and define the slicing properties to determine which hardware will receive them. Not all assets need to be contained in a variant group.
    3. I create an Addressable profile for sliced assets.
    4. I set the 'Build Path' for the profile and for each variant catalog to a path in the AssetDatabase (eg: "Assets/AddressableAssetsData/GeneratedBundles").
    5. I set 'Runtime Load Path' for the profile and for each variant catalog to "res://" (This only works properly on the fork mentioned above. The stock Addressables Library has a bug that prevents 'res://' from working as a load path.
    6. I configure my build to use BuildScriptPacketMultiCatalogResources. Resource collection is enabled by calling
    UnityEditor.iOS.BuildPipeline.collectResources += packedMulti.CollectResources;
    This will generate an iOS Asset Catalog in the resulting Xcode project that contains all of the asset bundles as well as the catalog indexes.
    7. In the 'Awake' of one of my scripts I call
    Addressables.LoadContentCatalogAsync("res://VariangGroupName", true);
    for each variant group that was defined. This loads the AssetCatalog index specific to the target device for that group.