Search Unity

Sprite atlasing and asset bundles issues

Discussion in 'Asset Bundles' started by kavalri_patrik, Jan 23, 2021.

  1. kavalri_patrik


    Dec 18, 2020
    I'd like to point out some (in my opinion) pretty big issues relating to sprite atlases and asset bundles.
    • Sprite atlases maintain references to the sprites that should be baked into them.
    • UI components and similar objects maintain references to the sprites.
    Here's a diagram that illustrates the situation:

    Untitled 4.png

    When building asset bundles, the sprites get baked into the atlas and the situation automagically gets resolved to something like this instead:

    Untitled 3.png

    • "Image Component" appears to not depend on "Sprite Atlas" in the editor, but in the built asset bundle it actually does.
    • "Image Component" appears to depend on "Texture file" in the editor, but in the built asset bundle it does not.
    Why is this problematic?

    Lets assume I, a developer, am tasked with assigning assets to bundles. I want to ensure that no assets gets duplicated when building the bundles. This requires me to assign all used assets to a bundle, so that Unity does not automatically include them in multiple bundles.

    I start by assigning the assets that we're interested in loading at runtime to bundles, and then I use AssetDatabase.GetDependencies() to find what other assets they reference. Now the first issue arises:
    • Sprite atlases do not get reported as a dependency by AssetDatabase.GetDependencies() because it cannot be reached via reference traversal from any asset. See the first diagram.
    Okay, but I'm a developer, so I can write some smart code that figures this out anyway. I simply have to find all SpriteAtlases, check which sprites have been assigned to them, and then substitute any dependency on their sprites with the corresponding SpriteAtlas instead. Thus:
    • I put UI.prefab in asset bundle called "user_interface".
    • I find that AssetDatabase.GetDependencies() reports AtlasedSprite.png as a dependency of UI.prefab.
    • I check my project's atlases and find that AtlasedSprite.png is indeed assigned to MasterAtlas.spriteatlas.
    • I therefore assign MasterAtlas.spriteatlas to the bundle "user_interface", and not AtlasedSprite.png.
    But unfortunately, this strategy fails. It turns out that someone had actually made a reference of type Texture2D to the source asset AtlasedSprite.png, because it needed to be applied as a texture on a mesh, and not used as a sprite. Thus the second issue:
    • To accurately predict whether a sprite atlas should be included in a bundle or not, we need to know if the reference was made to the sprite or to the texture.
    • There is no definite way (using the asset database tools) to tell whether an object depends on a texture or a sprite. It is therefore not possible to tell whether the object depends on the sprite atlas or not.
    • The problem lies in the fact that AssetDatabase.GetDependencies() only reports asset paths, while it is possible to have multiple assets at the same path. In this case, the main asset at the path is a Texture2D, and it has a Sprite sub-asset. A reference to either of them will appear the same to a developer calling AssetDatabase.GetDependencies().
    Alright, well, I can choose to ignore the issue by simply assuming that nobody will make a direct Texture2D reference to an atlased sprite, since it is not very common. But, what happens then is that:
    • If someone does make a Texture2D reference to an atlased sprite, I will be forced to assume that what they really wanted was the sprite (because I can't tell the difference), and therefore I should include the sprite atlas in the same bundle as the asset making the reference.
    • I may now have unintentionally included a very large atlas texture in the bundle, when all that was needed was a single small texture.
    • The texture file may end up being included in multiple bundles, because I did not explicitly assign it to any bundle - I assigned the sprite atlas instead.
    Lastly, the third issue: during development, we always want to try to keep our assets assigned to bundles to avoid duplication when we build, and this will become a problem when a previously unatlased sprite gets assigned to an atlas.
    • If an unatlased sprite is being referenced from multiple asset bundles, we will assign the texture file to a bundle to avoid having duplicates.
    • At some later point, someone decides to put that sprite in an atlas. Perhaps because it makes more sense to do so after adding more sprites that it could be atlased together with.
    • We now have the situation that the sprite's texture file has been included in a bundle, but it is no longer used by any other assets (since it's atlased), so it should not be in the bundle.
    • Any automated attempt to find this unnecessarily included texture file will fail, because, as mentioned, AssetDatabase.GetDependencies() will erroneously tell me that the texture file is in fact being used by something.
    We could try to find all atlased sprites that have been assigned to asset bundles, and unassign them from the bundle. But this will cause issues if someone actually needs to have that texture in the bundle so that it can be loaded raw as a Texture2D.

    The issues stem from the strange inverse dependency between SpriteAtlases and Sprites. On top of this, the fact that AssetDatabase.GetDependencies() has no knowledge of nested assets makes it impossible for us developers to counter it.

    This whole situation would be resolved if:
    • Atlased sprites are made into sub-assets of the atlas instead of sub-assets of the texture file.
    Because then any reference to an atlased sprite would cause the referencing asset to be dependent on the atlas, and it would be possible to tell these references apart from references to the texture itself. (I'd also like to point out that this would solve some other oddities, such as what happens if you assign a sprite to multiple atlases).

    Also, it would be great if someone at Unity could implement the following method:
    Code (CSharp):
    1. AssetIdentifier[] AssetDatabase.GetDependencies(AssetIdentifier assetIdentifier);
    Code (CSharp):
    1. struct AssetIdentifier
    2. {
    3.     public string guid;
    4.     public long localId;
    5. }
    So that us developers can make informed decisions about assets.

    Thank you!
    JeffersonTD likes this.