Search Unity

SpriteAtlas + AssetBundle unable to use sprites

Discussion in '2D' started by hectorSBM, Sep 9, 2017.

  1. hectorSBM

    hectorSBM

    Joined:
    Feb 4, 2017
    Posts:
    15
    I am attempting to load a prefabs (Projectile.prefab) from another AssetBundle (gun.unity3d). All the sprites and SpriteAtlas that are being used by the Projectile prefab are in another AssetBundle (vfx_projectiles.unity3d).

    The problem I am having is that the sprite is never shown on the prefab. This happens with other SpriteAtlas I am using in the project. If I delete the SpriteAtlas from the project and AssetBundle, I can see the Sprite on the prefab(s). I am using Unity 2017.1.0p5

    When I load the projectile, the sprite is missing. But the image is assigned:


    And I get this error when I click on the sprite via the SpriteRenderer:

    Also I get another error when I load the Unity scene where the assetbundle and prefab are needed:
    In the Editor,
    All the sprites are tagged with the Atlas name i.e (Projectiles)


    I also unchecked including the SpriteAtlas in a build, therefore; I should be loading it through an AssetBundle and/or resources



    Then I wait for my services to load, and attempt to load the atlases. This is during boot/load time of the application:
    Code (CSharp):
    1.  
    2.     /// <summary>
    3.     /// Loads sprite atlases from assets bundles
    4.     /// </summary>
    5.     public sealed class AtlasLoader : SBMBehaviour, IAtlasLoader
    6.     {
    7.         #region Members
    8.         /// <summary>
    9.         /// Reference to the resource service
    10.         /// </summary>
    11.         private IResourceService _resourceService;
    12.  
    13.         /// <summary>
    14.         /// Reference to the asset bundle service
    15.         /// </summary>
    16.         private IAssetBundleService _assetBundleService;
    17.  
    18.         /// <summary>
    19.         /// Collection of the sprite atlases and their callbacks to load
    20.         /// </summary>
    21.         private Dictionary<string, Action<SpriteAtlas>> _spriteAtlasToLoad;
    22.         #endregion
    23.  
    24.         #region Injection
    25.         /// <summary>
    26.         /// Inject the resource service
    27.         /// </summary>
    28.         [Inject]
    29.         public IResourceService ResourceService { get { return _resourceService; } set { _resourceService = value; } }
    30.  
    31.         /// <summary>
    32.         /// Inject the asset bundle service
    33.         /// </summary>
    34.         [Inject]
    35.         public IAssetBundleService AssetBundleService { get { return _assetBundleService; } set { _assetBundleService = value; } }
    36.         #endregion
    37.  
    38.         #region Life Cycle
    39.         /// <summary>
    40.         /// Allocate members and register for event
    41.         /// </summary>
    42.         protected override void Awake()
    43.         {
    44.             base.Awake();
    45.  
    46.             _spriteAtlasToLoad = new Dictionary<string, Action<SpriteAtlas>>();
    47.  
    48.             SpriteAtlasManager.atlasRequested += OnSpriteAtlasRequested;
    49.         }
    50.  
    51.         /// <summary>
    52.         /// Initialize members
    53.         /// </summary>
    54.         public void Initialize()
    55.         {
    56.             if (_resourceService == null)
    57.                 LogError("Resource service not initialized!");
    58.         }
    59.  
    60.         /// <summary>
    61.         /// Load SpriteAtlases if requested
    62.         /// </summary>
    63.         private void Update()
    64.         {
    65.             if (_spriteAtlasToLoad != null && _spriteAtlasToLoad.Count > 0 && _resourceService != null && _assetBundleService != null && _assetBundleService.Loaded) {
    66.                 foreach (KeyValuePair<string, Action<SpriteAtlas>> pair in _spriteAtlasToLoad) {
    67.                     string tag = pair.Key;
    68.                     Action<SpriteAtlas> onAtlasLoaded = pair.Value;
    69.                     string assetbundle = AssetBundleUtility.GetAssetBundleName(AssetBundleType.vfx, tag);
    70.  
    71.                     Log("Attempting to load atlas: {0} in AB: {1}", tag, assetbundle);
    72.  
    73.                     _resourceService.GetAsset<SpriteAtlas>(assetbundle, tag, (spriteAtlas) => {
    74.                         onAtlasLoaded(spriteAtlas);
    75.                     });
    76.                 }
    77.  
    78.                 _spriteAtlasToLoad.Clear();
    79.             }
    80.         }
    81.  
    82.         /// <summary>
    83.         /// Unregister for the event
    84.         /// </summary>
    85.         private void OnDestroy()
    86.         {
    87.             if (_spriteAtlasToLoad != null && _spriteAtlasToLoad.Count > 0)
    88.                 _spriteAtlasToLoad.Clear();
    89.  
    90.             SpriteAtlasManager.atlasRequested -= OnSpriteAtlasRequested;
    91.         }
    92.         #endregion
    93.  
    94.         #region Event Handling
    95.         /// <summary>
    96.         /// Handle the event when an atlas is requested
    97.         /// </summary>
    98.         /// <param name="tag">tag name of the sprite atlas</param>
    99.         /// <param name="onAtlasLoaded">callback after atlas has been loaded</param>
    100.         private void OnSpriteAtlasRequested(string tag, Action<SpriteAtlas> onAtlasLoaded)
    101.         {
    102.             Log("Loading Sprite atlas! SpriteAtlas: {0}", tag);
    103.  
    104.             if (_spriteAtlasToLoad != null && !_spriteAtlasToLoad.ContainsKey(tag))
    105.                 _spriteAtlasToLoad.Add(tag, onAtlasLoaded);
    106.         }
    107.         #endregion
    108.     }

    After doing all this, I tried to get the same sprite from the loaded Atlas via SpriteAtlas.GetSprite. It came back as a valid sprite and the same NullReferenceException occurred.
     
  2. hectorSBM

    hectorSBM

    Joined:
    Feb 4, 2017
    Posts:
    15
  3. cjuillard

    cjuillard

    Joined:
    Aug 2, 2013
    Posts:
    6
    I am experiencing what seems like the same problem. I am attempting to load a scene that exists in AssetBundle1 which contains references to sprites that I have atlased in AssetBundle2. When the scene loads, the sprites don't appear. I tried some similar debugging as you and have gotten things to show up correctly by doing a couple different things...

    1. Deleting the atlas altogether causes the sprites to appear correctly. So if there's no atlasing happening, it works.
    2. Explicitly loading the sprite atlas by calling AssetBundle.LoadAssetAsync before I load the scene causes the sprites to appear correctly.
    Seems like Unity isn't handling the packed sprites correctly across asset bundles.

    Edit: I am using 2017.1.1p1
     
  4. geretti

    geretti

    Joined:
    Dec 20, 2016
    Posts:
    11
    Same here, would love to know if Unity is aware of the issue, and if there's a fix or workaround.
     
  5. cjuillard

    cjuillard

    Joined:
    Aug 2, 2013
    Posts:
    6
    For now I've switched back to the legacy Sprite packing which seems to handle bundles correctly. I created a bug report for it with a stripped down project, I will post if I get any response from that.
     
    hectorSBM likes this.
  6. drhodor

    drhodor

    Joined:
    Aug 18, 2013
    Posts:
    39
    Having same issue, prefabs from bundle one don't have valid references to atlassed sprites in bundle 2

    Plz fix!!
     
  7. Kuan

    Kuan

    Unity Technologies

    Joined:
    Jul 2, 2014
    Posts:
    87
    Hello!

    @hectorSBM. It will be really helpful if you can send me your project (or if it's too big, a stripped down version which able to reproduce the issue). I created a simple project that I hope resembles your setup and it works for me, so, I really need to look into your project. I might be missing something. I attached my project, you can have a look and see if it works for you.

    Here are the key points to note it works for me.
    • Load Atlas bundle first then load prefab bundle. This will allow auto-relink (if atlas is marked as "Include in build")
    • If atlas has unchecked "Include in build" then you must install the callback. Or use "SpriteAtlas.GetSprite"

    Both examples are shown the sample project. If you can't send your project, please detail your order of execution.

    For asset bundle, it's better to test it with standalone player. In Editor, it can be misleading. This error is cause by Unity trying to locate the asset in the project (where the asset is loaded from the asset bundle).

    @cjuillard Your "different thing" no.2 is actually the solution for this kind of Asset Bundle dependency issue. Sprite depends on the Sprite Atlas if it is packed into one. So it need SpriteAtlas to be available before loading the Sprite.

    Asset bundle does not load for you any of its dependencies. You must do loading yourself somehow. See details here.

    That's why it works when you load the atlas bundle first.
     

    Attached Files:

  8. cjuillard

    cjuillard

    Joined:
    Aug 2, 2013
    Posts:
    6
    Thanks for the reply! From the link you referenced it says that both bundles need to be loaded before loading the material. In your example that means both bundles must be loaded and then you should be able to instantiate the prefab. It says nothing about needing to explicitly load the dependent texture. So carrying that same logic to your example you should not need to explicitly load the sprite atlas dependency. Is there something wrong with that logic? In 3.5.2 of this documentation it seems to back up that idea - https://unity3d.com/learn/tutorials/topics/best-practices/assetbundle-fundamentals

    From your example project...
    Code (CSharp):
    1. // Pre load the asset bundle with sprite atlas
    2. var atloadOp = new WWW("file://" + Application.streamingAssetsPath + "/" + saBundle);
    3. yield return atloadOp;
    4.  
    5. // I would think based on the documentation this should be unnecessary, right!? In my test
    6. // it was necessary. If you comment it out it actually works in the player, but if you make a
    7. // build it won't load properly.
    8. atloadOp.assetBundle.LoadAsset<SpriteAtlas>("sa");
    9.  
    10. // Then load the prefab
    11. var loadOp = new WWW("file://" + Application.streamingAssetsPath + "/" + prefabBundle);
    12. yield return loadOp;
    13.  
    14. var prepre = loadOp.assetBundle.LoadAsset<GameObject>("prop_crate_ammo");
    15. GameObject.Instantiate(prepre);
     
  9. hectorSBM

    hectorSBM

    Joined:
    Feb 4, 2017
    Posts:
    15
    @Kuan Thanks for the reply, I was taking a look at your project, and IT WORKED! Event thought the project you created was in 2017.3.0a6... the same concept worked, by loading the sprite atlas first. Kuan, If you are Unite Austin, I owe you your favorite drink!

    I feel for a feature, that you shouldn't have to load the SA if it already a dependency to another AssetBundle

    anyway, here are my changes:
    Code (CSharp):
    1.         #region Sprite Atlas
    2.         /// <summary>
    3.         /// Load the sprite atlas from an asset bundle
    4.         /// </summary>
    5.         /// <param name="tag">asset bundle to load</param>
    6.         /// <param name="onAtlasLoaded">callback when the asset is loaded</param>
    7.         private IEnumerator LoadAtlas(string tag, Action<SpriteAtlas> onAtlasLoaded)
    8.         {
    9.             if (_assetBundleService != null) {
    10.                 while (!_assetBundleService.Loaded)
    11.                     yield return null;
    12.  
    13.                 var operation = _assetBundleService.GetAssetAsync<SpriteAtlas>(AssetBundleUtility.GetAssetBundleName(AssetBundleType.vfx, string.Empty), tag);
    14.  
    15.                 if (operation != null) {
    16.                     yield return operation;
    17.  
    18.                     onAtlasLoaded(operation.Asset);
    19.                 }
    20.                 else
    21.                     LogError("Unable to get assetbundle for sprite atlas! Tag: {0}", tag);
    22.             }
    23.         }
    24.         #endregion
    25.  
    26.         #region Event Handling
    27.         /// <summary>
    28.         /// Handle the event when an atlas is requested
    29.         /// </summary>
    30.         /// <param name="tag">tag name of the sprite atlas</param>
    31.         /// <param name="onAtlasLoaded">callback after atlas has been loaded</param>
    32.         private void OnSpriteAtlasRequested(string tag, Action<SpriteAtlas> onAtlasLoaded)
    33.         {
    34.             Log("Loading Sprite atlas! SpriteAtlas: {0}", tag);
    35.  
    36.             StartCoroutine(LoadAtlas(tag, onAtlasLoaded));
    37.         }
    38.         #endregion
     
  10. fsClient

    fsClient

    Joined:
    Apr 5, 2018
    Posts:
    1
    About that bug, when are Unity planning to fix it?
    It is painful not to be able to check with Editor.
     
    forcepusher likes this.
  11. forcepusher

    forcepusher

    Joined:
    Jun 25, 2012
    Posts:
    227
    Hello. It's been 2 years. Are there any workarounds ?
    I really want to be able to test the game in Editor instead of making a build every time. It's critical for me because WebGL takes 4 minutes to build.
     
    Last edited: Jun 3, 2019
  12. forcepusher

    forcepusher

    Joined:
    Jun 25, 2012
    Posts:
    227
    Came up with this, but I wish there was a proper solution.
    Code (CSharp):
    1. using UnityEditor;
    2. using System.Collections.Generic;
    3. using System.IO;
    4. using System.Linq;
    5.  
    6. [InitializeOnLoad]
    7. public static class AtlasBugWorkaround
    8. {
    9.     static AtlasBugWorkaround()
    10.     {
    11.         EditorApplication.playModeStateChanged += OnPlayModeStateChanged;
    12.     }
    13.  
    14.     private static List<string> _spriteAtlasAssets;
    15.  
    16.     private static void OnPlayModeStateChanged(PlayModeStateChange state)
    17.     {
    18.         if (state == PlayModeStateChange.EnteredPlayMode)
    19.             HideAtlasAssets();
    20.  
    21.         if (state == PlayModeStateChange.ExitingPlayMode)
    22.             UnhideAtlasAssets();
    23.     }
    24.  
    25.     private static void HideAtlasAssets()
    26.     {
    27.         _spriteAtlasAssets = AssetDatabase.FindAssets("t:spriteatlas").Select(AssetDatabase.GUIDToAssetPath).ToList();
    28.  
    29.         foreach (var spriteAtlasAsset in _spriteAtlasAssets)
    30.         {
    31.             var fileName = Path.GetFileName(spriteAtlasAsset);
    32.             var directoryName = Path.GetDirectoryName(spriteAtlasAsset);
    33.            
    34.             FileUtil.MoveFileOrDirectory(spriteAtlasAsset, directoryName + "/." + fileName);
    35.             FileUtil.MoveFileOrDirectory(spriteAtlasAsset + ".meta", directoryName + "/." + fileName + ".meta");
    36.         }
    37.  
    38.         AssetDatabase.Refresh();
    39.     }
    40.  
    41.     private static void UnhideAtlasAssets()
    42.     {
    43.         foreach (var spriteAtlasAsset in _spriteAtlasAssets)
    44.         {
    45.             var fileName = Path.GetFileName(spriteAtlasAsset);
    46.             var directoryName = Path.GetDirectoryName(spriteAtlasAsset);
    47.  
    48.             FileUtil.MoveFileOrDirectory(directoryName + "/." + fileName, spriteAtlasAsset);
    49.             FileUtil.MoveFileOrDirectory(directoryName + "/." + fileName + ".meta", spriteAtlasAsset + ".meta");
    50.         }
    51.  
    52.         AssetDatabase.Refresh();
    53.     }
    54. }
     
    Last edited: Jun 3, 2019
  13. xucian

    xucian

    Joined:
    Mar 7, 2016
    Posts:
    846
    Any solution for this for 2019.3?
     
    chriscgogii, wtmsuperman and cygmatz like this.