Sprite Atlas assets duplication in bundles

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 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?

Case closed, it was just sprites metadata for Sprite Atlas that was added to bundles. It is mentioned in older addressables docs - Memory management | Addressables | 1.16.19

Tried to find it somewhere in latest docs, but I couldn’t.

1 Like

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://discussions.unity.com/t/797793

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.

4 Likes

Hello there!

I think the section here on “Addressable Sprites” explains it quite nicely. How Addressables interact with other project assets | Addressables | 1.19.19.

2 Likes

Here’s a link to the docs from addressables 1.21, where it got it’s own page:
https://docs.unity3d.com/Packages/com.unity.addressables@1.21/manual/editor/building-content/AddressablesAndSpriteAtlases.html

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.

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 :slight_smile:

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

using UnityEditor.Build;
using UnityEditor.Build.Reporting;
using UnityEngine;

namespace YourNamespace
{
    public class SpriteAtlasPostprocessBuild : IPostprocessBuildWithReport
    {
        public int callbackOrder { get; } = 0;

        public void OnPostprocessBuild(BuildReport report)
        {
            Debug.Log(
                "When loading the sprite atlases via Addressable, we should avoid they getting build into the app. " +
                "Otherwise, the sprite atlases will get loaded into memory twice.");

            Debug.Log("Set the `IncludeInBuild` flag to `true` for all sprite atlases after finishing the App build.");
            SpriteAtlasUtils.SetAllIncludeInBuild(true);
        }
    }
}
using UnityEditor.Build;
using UnityEditor.Build.Reporting;
using UnityEngine;

namespace YourNamespace
{
    public class SpriteAtlasPreprocessBuild : IPreprocessBuildWithReport
    {
        public int callbackOrder { get; } = int.MinValue;

        public void OnPreprocessBuild(BuildReport report)
        {
            Debug.Log("##### SpriteAtlas PreprocessBuild start ######");

            Debug.Log(
                "When loading the sprite atlases via Addressable, we should avoid they getting build into the app. " +
                "Otherwise, the sprite atlases will get loaded into memory twice.");

            Debug.Log("Set the `IncludeInBuild` flag to `false` for all sprite atlases before starting the App build.");
            SpriteAtlasUtils.SetAllIncludeInBuild(false);

            Debug.Log("##### SpriteAtlas PreprocessBuild end ######");
        }
    }
}
using System;
using System.Linq;
using UnityEditor;
using UnityEngine.U2D;

namespace YourNamespace
{
    public static class SpriteAtlasUtils
    {
        public static void SetAllIncludeInBuild(bool enable)
        {
            SpriteAtlas[] spriteAtlases = LoadSpriteAtlases();

            foreach (SpriteAtlas atlas in spriteAtlases)
            {
                SetIncludeInBuild(atlas, enable);
            }
        }

        private static void SetIncludeInBuild(SpriteAtlas spriteAtlas, bool enable)
        {
            SerializedObject so = new SerializedObject(spriteAtlas);
            SerializedProperty atlasEditorData = so.FindProperty("m_EditorData");
            SerializedProperty includeInBuild = atlasEditorData.FindPropertyRelative("bindAsDefault");
            includeInBuild.boolValue = enable;
            so.ApplyModifiedProperties();
            EditorUtility.SetDirty(spriteAtlas);
            AssetDatabase.Refresh();
        }

        private static SpriteAtlas[] LoadSpriteAtlases()
        {
            string[] findAssets = AssetDatabase.FindAssets($"t: {nameof(SpriteAtlas)}");

            if (findAssets.Length == 0)
            {
                return Array.Empty<SpriteAtlas>();
            }

            return findAssets
                .Select(AssetDatabase.GUIDToAssetPath)
                .Select(AssetDatabase.LoadAssetAtPath<SpriteAtlas>)
                .ToArray();
        }
    }
}
4 Likes

SpriteAtlas section in the current documentation

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.
3 Likes

Sorry for the bump, but here’s how we have it implemented. This have been verified both in Pc build and console.

  • Sprites atlas, marked as addressables.
  • Sprites included in the atlas ALSO marked as addressables.

As indicated in the documentation the actual sprites are put inside the atlas and the sprites referenced are only stored as metadata. We have both the atlas and the folder with its sprites in the same addressable group, packed together in the same asset bundle.

Also, the sprite atlas have “Include in build” set to true at all times, we don’t toggle on for building the addressables and off for game builds. Unity automatically detects if an atlas is addressable and builds it inside a bundle or as part of the build as mentioned in this thread

All our scenes are addressable, besides a bootstrap scene, and scenes are the only thing in the whole project that use any kind of “Addressable Load” (Addressables.LoadSceneAsync in this case). Nothing else use any Addressable.Load at all. All prefabs and sprites are directly included in the scenes or ar instantiated at runtime by pools inside the scene.

With this configuration, sprites and atlases are only built in the bundles, not in the player, and memory profiler do not show any duplication at all. Furthermore, changing scenes that use different sprites/prefabs and running the memory profiler again shows that atlases that were previously loaded are now gone as expected.

So long story short:

    1. Mark atlas as addressable and set Include in build to true.
    2. Mark sprites included in the atlas as addressable too.
    3. Mark scenes as addressable and load them using Addressables.LoadSceneAsync.
    4. Trust the system.
1 Like

hey @SVC-Games , nice to know it works like that on your setup.

Your use is, I would say, very uncommon, since the only usage of “Addressables Loading” are related to loading the scenes.

I’m not sure if your steps would work when we also load ScriptableObjects (containing Sprites) or when we load Prefabs also containing sprites already in place and also loading sprites after the prefab is already in place.

It would be nice if the Unity Engineers could confirm for us the correct steps to go.

For now, I will anyway keep my setup since I have a Pre/Post build processor to take care of it and it works well for Android, iOS, PC, Consoles:

  • 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.

Yes, we use scriptable objects with our pools, that define which prefabs need to be instantiated. Those scriptable objects and the prefabs are addressables too.

All our prefabs have sprites already in place (and animations) and it works ok.

As I mentioned, “Include in build” seems to work differently if the atlas is addressable. In our case, it works as expected: It’s included when building the asset bundles, but not included (again) in the game build

1 Like