Search Unity

Resolved Sprite Atlas assets duplication in bundles

Discussion in 'Addressables' started by Dre0Dru, Dec 5, 2021.

  1. Dre0Dru

    Dre0Dru

    Joined:
    Nov 16, 2015
    Posts:
    9
    Hello! I have a problem pairing Spite Atlases and Addressables.
    Current flow that I am testing:
    1. Empty bootstrap scene that is embedded into build. It then loads target scene from Addressables.
    2. Target scene from Addressables contains some UI prefabs that use sprites packed in Sprite Atlas.
    3. Sprite Atlas has Include in Build flag set to false, Sprite Atlas asset is marked as Addressable asset. It is loaded when SpriteAtlasManager.atlasRequested event triggers. I pass loaded Sprite Atlas. to Action<SpriteAtlas> callback.
    4. Use Memory Profiler to check that only Sprite Atlas is loaded in target scene.

    First setup that works semi-correctly:
    1. No sprites from Sprite Atlas are marked as Addressable assets.
    Sprite Atlas works as intended, only Sprite Atlas is loaded into memory when target scene is loaded.
    But that introduces assets duplication, because sprite assets are included both in Sprite Atlas bundle and in Scene bundle.

    Seconds setup that removes assets duplication:
    1. Mark all sprites that are included in Sprite Atlas as Addressable assets. Add them to separate group or to group with Sprite Atlas
    That eliminates assets duplication in bundles, but it breaks Sprite Atlas behaviour. When target scene is loaded the result is that both Sprite Atlas and individual sprites are loaded into memory.

    So what am I doing wrong? How to use Sprite Atlases correctly with Addressables without introducing assets duplication?
     
  2. Dre0Dru

    Dre0Dru

    Joined:
    Nov 16, 2015
    Posts:
    9
    Last edited: Dec 30, 2021
    dirty-rectangle likes this.
  3. SVC-Games

    SVC-Games

    Joined:
    May 21, 2013
    Posts:
    137
    So which one is the correct one?
    - A) Do NOT mark the sprites contained in the atlas as addressables and mark ONLY the Atlas as addressable
    or
    - B) Mark both sprites and Atlas as addressable in the same group and sprites are only "metadata"?

    Also, "include in build" shouldn't be marked according to this post? https://forum.unity.com/threads/sprite-atlas-include-in-build-option.921029/
     
  4. Dre0Dru

    Dre0Dru

    Joined:
    Nov 16, 2015
    Posts:
    9
    Hello!

    "A" is the correct one:
    1. Mark Atlas as Addressable
    2. Do NOT mark sprites contained in the Atlas as Addressables

    Everything would work, but there will be one "issue".
    Since now sprites metadata will be included in other bundles that reference sprites from Atlas, Addressables Analyze tool will give warnings about duplicated dependencies.
    To calm Analyze tool down, you can create sort of "dump" Addressable group with all sprites from Atlas included there, but uncheck "Include in Build" option in group. That way individual sprites still won't be included in build, but Analyze tool won't give warnings.
     
    Last edited: Dec 30, 2021
  5. agasser

    agasser

    Joined:
    Nov 25, 2018
    Posts:
    7
    v01pe_ and SergeiYaremaTwigames like this.
  6. v01pe_

    v01pe_

    Joined:
    Mar 25, 2015
    Posts:
    71
  7. sandolkakos

    sandolkakos

    Joined:
    Jun 3, 2009
    Posts:
    285
    Thanks for the discussion here, guys. After reading and testing a lot about SpriteAtlas duplicated into memory, I just found this Article: https://support.unity.com/hc/en-us/articles/360000665546, and realized how to solve my specific problem.

    My problem was:
    1. Both my SpriteAtlas A and B were getting loaded into memory in my Login scene, even when the login scene uses sprites only from Sprite Atlas A.
    2. At the same time, the SpriteAtlas A was duplicated into memory, using 32 MB of Ram since its size is 16 MB.

    upload_2023-7-17_18-59-27.png


    Also, when loading the next scene, which really uses both SpriteAtlases, both of them were duplicated.

    To solve that issue, my setup looks like that:
    - Mark the SpriteAtlas as Addressable.
    - Mark all the source sprites as Addressable as well.
    - They are in the Textures group, which has the `Build Mode` set to `Pack Separately`, so that the source sprites go into bundle A and the SpriteAtlas will go into bundle B.

    When building the Addressables, the SpriteAtlas need to have the `Include In Build` flag set to true.
    When building the App, it needs to be set to false.

    Now, when I use the `Memory Profiler` to inspect the assets loaded into memory, I do not see the SpriteAtlas duplicated anymore :)

    upload_2023-7-17_18-59-38.png

    In case you find it useful, just add those Pre/Postprocess build scripts and then everything will be automatic:

    Code (CSharp):
    1. using UnityEditor.Build;
    2. using UnityEditor.Build.Reporting;
    3. using UnityEngine;
    4.  
    5. namespace YourNamespace
    6. {
    7.     public class SpriteAtlasPostprocessBuild : IPostprocessBuildWithReport
    8.     {
    9.         public int callbackOrder { get; } = 0;
    10.  
    11.         public void OnPostprocessBuild(BuildReport report)
    12.         {
    13.             Debug.Log(
    14.                 "When loading the sprite atlases via Addressable, we should avoid they getting build into the app. " +
    15.                 "Otherwise, the sprite atlases will get loaded into memory twice.");
    16.  
    17.             Debug.Log("Set the `IncludeInBuild` flag to `true` for all sprite atlases after finishing the App build.");
    18.             SpriteAtlasUtils.SetAllIncludeInBuild(true);
    19.         }
    20.     }
    21. }
    22.  
    Code (CSharp):
    1. using UnityEditor.Build;
    2. using UnityEditor.Build.Reporting;
    3. using UnityEngine;
    4.  
    5. namespace YourNamespace
    6. {
    7.     public class SpriteAtlasPreprocessBuild : IPreprocessBuildWithReport
    8.     {
    9.         public int callbackOrder { get; } = int.MinValue;
    10.  
    11.         public void OnPreprocessBuild(BuildReport report)
    12.         {
    13.             Debug.Log("##### SpriteAtlas PreprocessBuild start ######");
    14.  
    15.             Debug.Log(
    16.                 "When loading the sprite atlases via Addressable, we should avoid they getting build into the app. " +
    17.                 "Otherwise, the sprite atlases will get loaded into memory twice.");
    18.  
    19.             Debug.Log("Set the `IncludeInBuild` flag to `false` for all sprite atlases before starting the App build.");
    20.             SpriteAtlasUtils.SetAllIncludeInBuild(false);
    21.  
    22.             Debug.Log("##### SpriteAtlas PreprocessBuild end ######");
    23.         }
    24.     }
    25. }
    26.  


    Code (CSharp):
    1. using System;
    2. using System.Linq;
    3. using UnityEditor;
    4. using UnityEngine.U2D;
    5.  
    6. namespace YourNamespace
    7. {
    8.     public static class SpriteAtlasUtils
    9.     {
    10.         public static void SetAllIncludeInBuild(bool enable)
    11.         {
    12.             SpriteAtlas[] spriteAtlases = LoadSpriteAtlases();
    13.  
    14.             foreach (SpriteAtlas atlas in spriteAtlases)
    15.             {
    16.                 SetIncludeInBuild(atlas, enable);
    17.             }
    18.         }
    19.  
    20.         private static void SetIncludeInBuild(SpriteAtlas spriteAtlas, bool enable)
    21.         {
    22.             SerializedObject so = new SerializedObject(spriteAtlas);
    23.             SerializedProperty atlasEditorData = so.FindProperty("m_EditorData");
    24.             SerializedProperty includeInBuild = atlasEditorData.FindPropertyRelative("bindAsDefault");
    25.             includeInBuild.boolValue = enable;
    26.             so.ApplyModifiedProperties();
    27.             EditorUtility.SetDirty(spriteAtlas);
    28.             AssetDatabase.Refresh();
    29.         }
    30.  
    31.         private static SpriteAtlas[] LoadSpriteAtlases()
    32.         {
    33.             string[] findAssets = AssetDatabase.FindAssets($"t: {nameof(SpriteAtlas)}");
    34.  
    35.             if (findAssets.Length == 0)
    36.             {
    37.                 return Array.Empty<SpriteAtlas>();
    38.             }
    39.  
    40.             return findAssets
    41.                 .Select(AssetDatabase.GUIDToAssetPath)
    42.                 .Select(AssetDatabase.LoadAssetAtPath<SpriteAtlas>)
    43.                 .ToArray();
    44.         }
    45.     }
    46. }
    47.  
     
    avrahamy likes this.
  8. DeathPro

    DeathPro

    Joined:
    Jul 28, 2018
    Posts:
    91
  9. sandolkakos

    sandolkakos

    Joined:
    Jun 3, 2009
    Posts:
    285
    Is the info there enough?

    I guess they are missing something like:
    - When building the Addressables, the SpriteAtlas needs to have the `Include In Build` flag set to true.
    - When building the App, it needs to be set to false.
     
    DeathPro likes this.