3
votes

I'm struggling with an issue in Unity loading Sprites from a SpriteAtlas, downloaded in an AssetBundle.

In our current game I am trying to implement AssetBundles to remove "Resources" folder usage, and reduce memory overhead (among other things).

In the Game app, the downloaded sprites aren't rendering correctly when running in the editor, so I built a small test project to better understand the problem. Unfortunately the test project works perfectly, even though I'm using identical code to download and display the sprites. I'll refer to these two versions as TestApp and GameApp from here on. Just to reiterate, this issue is only a problem when running in the Editor (not the final device builds), however this is a game breaker for us because we simply can't develop and test the application. The turnaround getting builds to device is simply too long compared to running in the Editor.

A simplified version of the script that I use for loading asset bundles is as follows. (This has been hugely simplified for brievity, including stripping out all object caching and error handling, etc)

public IEnumerator GetSpriteFromBundle(string bundleURL, string spriteAtlasName, string spriteName, Action<Sprite> onLoadAction)
{
    //  get the AssetBundle
    AssetBundle bundle = null;
    UnityWebRequest request = UnityWebRequestAssetBundle.GetAssetBundle(bundleURL);
    yield return request.SendWebRequest();
    if (!request.isNetworkError && !request.isHttpError)
    {
        bundle = DownloadHandlerAssetBundle.GetContent(request);
    }

    //  Get the SpriteAtlas
    SpriteAtlas atlas = null;
    if (bundle != null)
    {
        if (bundle.Contains(spriteAtlasName))
        {
            AssetBundleRequest assetRequest = bundle.LoadAssetAsync<SpriteAtlas>(spriteAtlasName);
            yield return assetRequest;
            if (assetRequest.isDone)
            {
                atlas = assetRequest.asset as SpriteAtlas;
            }
        }
    }

    //  Get the Sprite
    Sprite sprite = null;
    if (atlas != null)
    {
        sprite = atlas.GetSprite(spriteName);
    }
    onLoadAction(sprite);
}

The script that I use to call this to load the Sprite is as follows, (again error handling is stripped out):

public void Start()
{
    UnityEngine.UI.Image displayImage = GameObject.Find("Path/To/ImageObject").GetComponent<UnityEngine.UI.Image>();
    StartCoroutine(
         GetSpriteFromBundle(
            "https://mycdn.com/myassetbundles/gamesprites",      // AssetBundleURL
            "GameSprites",          //    SpriteAssetName
            "Icon1",                      //    SpriteName
            (sprite) =>
            {
                displayImage.sprite = sprite;
            })
        );
}

The end result of this is that everything works and loads correctly in the TestApp, but when playing the GameApp in the editor, the sprites are either invisible, or display as a weird image with 3 squares in it.

The only difference that I can see is that when I use the frame debugger to look at the differences between the TestApp and the GameApp, the TestApp shows the SpriteAtlas texture in the batch, but the GameApp does not.

As you can see here in the TestApp, the Texture is correctly set. TestApp - Texture is correctly set

And here in the GameApp, the texture is not set GameApp - Texture is not set

Things that I have checked and confirmed between versions

  • Neither the GameApp nor the TestApp has any errors or exceptions.
  • It works correctly when built and deployed to a device (Only tested on Android so far)
  • A sprite object IS being returned in the onLoadAction callback in the GameApp.
  • I'm using the same AssetBundles and Sprites in both applications.
  • I've done side by side comparisons of the Image object settings in the inspector in both apps.
  • Both apps are set to the same build platform (I've tried Android, WebGL, and StandaloneWindows, and all have the same result)
  • The AssetBundles are built for the correct build platform (as above)

The only difference that I can see between the TestApp and the GameApp is that the GameApp is much larger / more complex, and it has a scene change (we start with a loading scene before going to the in-game scene), but I don't see how either of those should affect anything.

I've also set up and tested a version using AssetBundle.LoadFromFileAsync() and loading the file from the StreamingAssets folder, with the same results

So, my questions: Is this a bug in the Unity Editor? What should I be looking at to try and fix this? We basically can't use AssetBundles until I find a solution.

I've used the AssetBundleBrowser asset to set up the AssetBundles.

I've tested with various versions of Unity, from older 2018.1 releases up to the latest release (2018.2.7f1 at the time of writing).

(Cross posted from the Unity Forums)

--- Update ---

It's been mentioned that this is a duplicate question this question , however I am asking an entirely different question. My code works correctly on a device, but does not work in the Unity Editor.

I have also tried restructuring my code to query for a Sprite rather than a SpriteAtlas, and using the LoadAssetWithSubAssetsAsync method, with the following code, and I am still having the same end result of no sprite being displayed in the editor.

private IEnumerator GetSpriteFromBundle(string bundleURL, string spriteName, Action<Sprite> onLoadAction)
{
    //  get the AssetBundle
    AssetBundle bundle = null;
    UnityWebRequest request = UnityWebRequestAssetBundle.GetAssetBundle(bundleURL);
    yield return request.SendWebRequest();
    if (!request.isNetworkError && !request.isHttpError)
    {
        bundle = DownloadHandlerAssetBundle.GetContent(request);
    }

    //  Get the Sprite
    Sprite sprite = null;
    if (bundle != null)
    {
        if (bundle.Contains(spriteName))
        {
            AssetBundleRequest assetRequest = bundle.LoadAssetWithSubAssetsAsync<Sprite>(spriteName);
            yield return assetRequest;
            if (assetRequest.isDone)
            {
                for (int i = 0; i < assetRequest.allAssets.Length; i++)
                {
                    sprite = assetRequest.allAssets[i] as Sprite;
                    if (sprite != null && sprite.name == spriteName)
                    {
                        onLoadAction(sprite);
                        yield break;
                    }
                }
            }
        }
    }
    onLoadAction(null);
}
1
There is no official example from the doc for loading sprite atlas from AssetBundle. This made people to start using SpriteAtlas for that. I think that's wrong. 1. You load it as Sprite not SpriteAtlas. 2. You do so with the LoadAssetWithSubAssetsAsync function not the LoadAssetAsync function. 3. To access the loaded Sprite atlas, you don't use assetRequest.asset, you use assetRequest.allAssets which returns array of all the Sprite atlas. See duplicate for full working example. - Programmer
I've attempted your suggestion of querying for a Sprite directly (rather than a SpriteAtlas) and still have the same end result. - Buzzrick
Your original question says that it doesn't work in the Editor. After trying my solution, the edit says that it doesn't work in a browser. I am not sure what you mean by that or what your current problem is now... - Programmer
Oh, good spotting, sorry for the confusion. I meant that it doesn't work in the Editor in play mode, (not a browser). - Buzzrick
In your Update in your question, you need to add the new code you'r using followed by how you're using it. - Programmer

1 Answers

2
votes

It turns out that the problem was caused by the SpritePacker settings.

If I set the SpritePacker mode (in Edit->Project Settings->Editor) to "Enabled for Builds" then the sprites aren't loaded properly, whereas if I set it to "Always Enabled" (the default I believe) then the sprites, and the SpriteAtlas is loaded correctly from the AssetBundle.

(I've raised this as a bug with Unity, but haven't heard a response yet).