Search Unity

(bug) Multi-page sprite atlas doesn't export/import correctly

Discussion in 'Addressables' started by nik_d, Mar 21, 2019.

  1. nik_d

    nik_d

    Joined:
    Apr 27, 2018
    Posts:
    50
    Just prepared minimal project showing the bug:
    https://drive.google.com/open?id=1WNzCd0HTV9cCaanZn7toHooAfQkBEV_s
    • 5 sprites are placed to one atlas that is split by 2 pages
    • One addressable group with one atlas asset
    • SampleScene.unity via Main.cs on Start() loads the atlas via addressables and creates Image per sprite
    • Play in "Fast Mode" (and "Virtual Mode") shows all five sprites
    • Play in "Packed Mode" (after "Build Player Content") shows only sprites from the first atlas page
      • (on load last `sprite.texture` is null, on export .resS file in BuildCache contains only first atlas page texture)


    Is there any workaround for this bug?
     

    Attached Files:

  2. nik_d

    nik_d

    Joined:
    Apr 27, 2018
    Posts:
    50
    Hope this can help someone.)

    As "temporary" workaround for this bug I've modified class ArchiveAndCompressBundles (com.unity.scriptablebuildpipeline@1.3.5-preview\Editor\Tasks\ArchiveAndCompressBundles.cs - yes, we keep some packages in sources because of deep integration with them) to build bundles with only .spriteatlas assets inside via old good BuildPipeline. =)

    Before line
    for (int i = 0; i < bundleResources.Count; i++)
    collect bundles to build via BuildPipeline:
    Code (CSharp):
    1.             ////>addressables: atlas building workaround /nik
    2.             var bundleToAtlasWriteOperation = m_WriteData.WriteOperations
    3.                 .OfType<AssetBundleWriteOperation>()
    4.                 .Where(x => x.Info.bundleAssets.Any(a => a.address.EndsWith(".spriteatlas")))
    5.                 .ToDictionary(x => x.Info.bundleName);
    6.             { // detect bundles containing not only atlases
    7.                 foreach (var kv in bundleToAtlasWriteOperation) {
    8.                     if (kv.Value.Info.bundleAssets.Any(a => !a.address.EndsWith(".spriteatlas"))) {
    9.                         throw new Exception($"Bundle {kv.Key} contains not only atlases: failed to apply atlas building workaround");
    10.                     }
    11.                 }
    12.             }
    13.             ////<addressables: atlas building workaround /nik
    14.  
    Replace lines of simple scriptable build pipeline's archiving

    details.Crc = ContentBuildInterface.ArchiveAndCompress(resourceFiles, writePath, compression);
    details.Hash = CalculateHashVersion(fileOffsets, resourceFiles);

    with selection
    Code (CSharp):
    1.                     ////>addressables: atlas building workaround /nik
    2.                     if (!bundleToAtlasWriteOperation.TryGetValue(bundleName, out var assetBundleWriteOperation)) {
    3.                         // usual scriptable build pipeline's bundle creation
    4.                         details.Crc = ContentBuildInterface.ArchiveAndCompress(resourceFiles, writePath, compression);
    5.                         details.Hash = CalculateHashVersion(fileOffsets, resourceFiles);
    6.                     } else {
    7.                         // bundle building via old good BuildPipeline
    8.                         var build = new AssetBundleBuild {
    9.                             assetBundleName = bundleName,
    10.                             assetNames = assetBundleWriteOperation.Info.bundleAssets.Select(a => a.address).ToArray(),
    11.                         };
    12.                         const BuildAssetBundleOptions options = BuildAssetBundleOptions.DeterministicAssetBundle | BuildAssetBundleOptions.StrictMode;
    13.                         Debug.Log($"Building bundle via old BuildPipeline: {writePath}");
    14.                         var manifest = BuildPipeline.BuildAssetBundles(Path.GetDirectoryName(writePath), new[] {build}, options, m_Parameters.Target);
    15.                         var bundles = manifest.GetAllAssetBundles();
    16.                         if (bundles.Length != 1 || bundles[0] != bundleName) {
    17.                             throw new Exception($"Failed to build bundle: {writePath}");
    18.                         }
    19.                         if (!BuildPipeline.GetCRCForAssetBundle(writePath, out var crc)) {
    20.                             throw new Exception($"Failed to get CRC from bundle: {writePath}");
    21.                         }
    22.  
    23.                         details.Crc = crc;
    24.                         details.Hash = manifest.GetAssetBundleHash(bundleName);
    25.                     }
    26.                     ////<addressables: atlas building workaround /nik
    27.  
    After that:
    * delete Library/BuildCache
    * make "Addressables->Build->Build Content", build app => everything good in built app
    * enable "Project Settings->Editor->Sprite Packer Mode->Always Enabled" (otherwise there'll be bug in Editor with sprites from bundles, but not in the built app), Play in "Packed Mode" => everything good in editor
     

    Attached Files:

    dimino likes this.
  3. Ryanc_unity

    Ryanc_unity

    Unity Technologies

    Joined:
    Jul 22, 2015
    Posts:
    249
    Hu...crazy work around. There is a minor edge case in that the SpriteAtlas bundle built with BuildPipeline can't depend on anything external, but SBP bundles can depend on that bundle and if you only have a SpriteAtlas in there, that should be just fine.

    I did just finish fixing this bug on our end, but it is an editor change, so we have to go through the normal editor release process to get it fixed. Will follow up when I have word which versions will contain the fix.
     
  4. nik_d

    nik_d

    Joined:
    Apr 27, 2018
    Posts:
    50
    Thanks a lot - we look forward to a normal solution.. but have no other choice right now (atlases should be updated and cannot be embedded with variants).

    I understand dependency issue but we can be sure building only atlases in a bundle just replacing
    BuildScriptPackedMode.GenerateBuildInputDefinitions
    content with:
    Code (CSharp):
    1.         static void GenerateBuildInputDefinitions(List<AddressableAssetEntry> allEntries, List<AssetBundleBuild> buildInputDefs, string groupName, string address)
    2.         {
    3.             var scenes = new List<AddressableAssetEntry>();
    4.             var assets = new List<AddressableAssetEntry>();
    5.             var atlases = new List<AddressableAssetEntry>();
    6.             foreach (var e in allEntries)
    7.             {
    8.                 if (e.AssetPath.EndsWith(".unity"))
    9.                     scenes.Add(e);
    10.                 else if (e.AssetPath.EndsWith(".spriteatlas"))
    11.                     atlases.Add(e);
    12.                 else
    13.                     assets.Add(e);
    14.             }
    15.             if (assets.Count > 0)
    16.                 buildInputDefs.Add(GenerateBuildInputDefinition(assets, groupName + "_assets_" + address + ".bundle"));
    17.             if (scenes.Count > 0)
    18.                 buildInputDefs.Add(GenerateBuildInputDefinition(scenes, groupName + "_scenes_" + address + ".bundle"));
    19.             if (atlases.Count > 0)
    20.                 buildInputDefs.Add(GenerateBuildInputDefinition(atlases, groupName + "_atlases_" + address + ".bundle"));
    21.         }
    22.  
    (although my local version of
    BuildScriptPackedMode.ProcessGroup
    is little more complex to support special PackSeparatelyByConfigs mode allowing to merge >20k assets to just several hundreds bundles :))
     
    Last edited: Mar 25, 2019