3

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);
}
Buzzrick
  • 823
  • 9
  • 21
  • 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 Sep 25 '18 at 06:18
  • I've attempted your suggestion of querying for a Sprite directly (rather than a SpriteAtlas) and still have the same end result. – Buzzrick Sep 26 '18 at 05:56
  • 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 Sep 26 '18 at 06:05
  • Oh, good spotting, sorry for the confusion. I meant that it doesn't work in the Editor in play mode, (not a browser). – Buzzrick Sep 26 '18 at 22:58
  • In your Update in your question, you need to add the new code you'r using followed by how you're using it. – Programmer Sep 27 '18 at 01:47
  • Can you please un-mark this as a duplicate. It is not a duplicate, but as such is hidden from view and no one can see it. – Buzzrick Sep 30 '18 at 21:11
  • I see no reason to re-open this yet. I will if you actually try what's in the duplicate but it's not working for you. Please, get the `LoadAsset` function in the duplicate, read that and simply use it to test loading the file. Forget about what you have now. Just use that from the duplicate and try to load a **local** assetbundle. See if that works or not. Once you get that working then you can go changing things around. If it doesn't work, replace the code in your question with the one in the duplicate then show how you're using or calling it. Also, show the current issue you have – Programmer Oct 01 '18 at 02:27
  • You may wonder why I said this, well that's because I found so many issues in your `GetSpriteFromBundle` code. You're supposed to exit that function if `request.isNetworkError && request.isHttpError` is true. You're not doing this and if the request fails, the assebundle loading will carry on too which is wrong. *Just the duplicate code like it is then let me know if there is a problem*. If possible post screenshot of the problem – Programmer Oct 01 '18 at 02:35
  • As I mentioned in my initial notes, I have stripped all caching and error handling code out of my code snippets for brevity. I have tried the equivalent of the code in your example, with the same results. I have also tried loading a local AssetBundle with the same results – Buzzrick Oct 01 '18 at 04:15

1 Answers1

2

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

Buzzrick
  • 823
  • 9
  • 21