Search Unity

  1. Unity 2020.2 has been released.
    Dismiss Notice
  2. Good news ✨ We have more Unite Now videos available for you to watch on-demand! Come check them out and ask our experts any questions!
    Dismiss Notice

Working Multi-Resolution Sprite Sheets

Discussion in '2D' started by TheFuntastic, Oct 18, 2014.

  1. WiedemannD


    Mar 4, 2015
    Another question I wanted to ask: In your examples before you packed the scene file (with the refs to sprites set) also in a bundle. I guess, because like that, the referenced sprites/textures would not be included in the main build, as they would be handled as dependencies, right?

    Will it in 5.2 (or maybe a patch release before that) also be possible to handle scene loading without having to pack them in separate bundles AND without pulling all in the scenes referenced sprites/textures into the main build, as long as they are available via asset bundles? So when NOT packing scenes in bundles, but just sprites/textures the app would not again hold two duplicates of the textures?
  2. ColossalPaul


    Unity Technologies

    May 1, 2013
    Yes, this is possible, if search for my 2nd example in this thread, you will find a project where the scene file is not packed in bundle. However, you do need to work with prefabs in bundle. Take a look.
  3. ColossalPaul


    Unity Technologies

    May 1, 2013
    Actually I checked again... this should already be in 5.1.2. Please check it out and post a bug report if it still doesn't work.

    With the bug fixed, you don't need this yes?
  4. ColossalPaul


    Unity Technologies

    May 1, 2013
    I triple checked again... it's actually 5.1.2p1, which should come out any day now. Hang in there for a day or 2.
  5. WiedemannD


    Mar 4, 2015
    Thanks for the quick response. And awesome that the atlas/original texture fix comes with the next patch!

    I'll check out your two examples again and then choose the better solution for us, though I have to admit both ways still feel a bit like a workaround.

    You think that sometime, there will be a solution for "manual loading, but auto referencing assets", when they were previously loaded via bundle, without the need to bundle the referencing scene OR prefab? And maybe giving the dev somehow the option to exclude referenced assets from the main build? Something like a "manual load" checkbox next to a reference slot, that can be checked, if the dev will take care of loading the asset beforehand but the reference will be set automatically via name/GUID/instanceID or whatever. In script this could be maybe handled via some "ManualLoad(name/GUID/instanceID)]" attribute. At least to me this would sound a bit more straight forward.

    Anyway, thanks again!
  6. Kiori


    Jun 25, 2014
    No, its not worth it.
    Even 'Base' engines like coco2d handle hd/sd(variants) a lot better(easier/simpler).
    Unity3D has always been Unity-not2D. Anything else is just hype, really. If you wanna save time this is not for you.At all.
    Also, in the standard features that the engine offers, without additional assets, you wont see many of the features that 2d engines consider 'basic'. Like a tilemap editor.
    These features it seems, are finally coming, check the new unity roadmap for more information. But when will they be here? I'd check back on unity in 1-2 years really, it should be all you would want/could ask for, by then.

    For me, i like unity, and its policy, its way of being, c#, unparalleled cross-platform support/distribution, etc.
    Also, i always use some 3d in 2d, and i really like the new std shader.
    I actually have a lot of reasons that would make a long list, to why use unity, and not anything else. But they are all related to the engine, and -not- unfortunately anything 2d. So its a choice.

    In my post, in page 2, I shared a mostly plug-n-play script that handled the loading of sd/hd assets, its very simple and based on the tuts Mike Geig released a while back(that got removed), it checks for a res. below X.Y and applies the given bundle. You're welcome to use it, and msg me with any sort of questions at any time.
    I'm no expert, but i share what i know. The script was just a base test/trial/example btw, if I was remaking it now, i'd do very different things with it,.(like using createFF and the streamA folder for local solutions)

    Personally though, I wont be messing with bundles for a few months, I want to wait and see how this whole thing pans out.
    I really do hope they make it all more interface friendly for unity newcomers, and workflow efficient for everyone else.
    We'll see.

    Good luck.
    theANMATOR2b likes this.
  7. ColossalPaul


    Unity Technologies

    May 1, 2013
    This is one of our goals, and we are starting with sprite packing. My predecessors started with the idea that everything should be automagic. That turns out didn't quite fit everyone's needs and no one seems to know what is going on or what's happening or where things are.

    So our new goal is to design systems that could be fully automated if you want and welcomes manual intervention. More 'interface friendly' like you put it.
  8. hexagonius


    Mar 26, 2013
    Hey there,

    I was wondering, if this thread was still on, because we are also looking for a good solution to the SD/ HD problem. I just wanted to throw in an idea on that one:

    Like layers, Unity could maintain an extendable list of resolution names. Based on those, a new SpriteRenderer Component (MultispriteRenderer ? Or just a toggle on the original one) could show multiple Sprite References, one for each resolution. The SpritePacker would then pack all sprites for each resolution separately. By calling some Application.SetResolution(string resolution) in the first Awake() one could define which atlas to load prior to loading a scene using SpriteRenderers.
    It would make things so incredibly easy. It would remove the need of dereferencing Sprites, the need of specific folder structures, and the need of manually packing atlases into bundles.
    Kiori likes this.
  9. ColossalPaul


    Unity Technologies

    May 1, 2013
    Doesn't seem scalable when you have a big project. There will be hundreds or thousands of renderers each you will then have to assign sprites for each platform. And every time you wish to reference something else, you'll have to do it once for each platform. Then there's merge issues since you will be modifying scene files.

    it may be an easier concept to comprehend, trust me, i went down that (those) road(s) (multiple references, @2/3/4x, hidden files, fully manual, and many more).

    But we have not given up yet to improve the tooling around this aspect to make it easier to comprehend. Stay tuned for updates in the near future.
  10. hexagonius


    Mar 26, 2013
    Fair enough, I guess we'll wait then ;)
  11. WiedemannD


    Mar 4, 2015
    A side note:

    I'm also using some plugins (e.g. Puppet2D) that don't use SpriteRenderers but SkinnedMeshRenderers. As far as I can see, SkinnedMeshRenderers are not able to use Unity's Sprite Atlases. For a mobile app I obviously needed atlases in these cases as well, but I did not want to use a third party tool just for these. So I wrote some kind of Unity Atlas Export Tool (way too complex, because you don't get read access to Unity Atlases' pixels), that spits out rebuild atlas PNGs based on Unity's packed atlases (thanks to tmsduman's help and Is this maybe of interest to someone else?

    Bildschirmfoto 2015-08-17 um 11.54.10.png

    Also two features which would make my exporter obsolet, but would be a lot better:
    - Unity Atlas support somehow in other renderers like SkinnedMeshRenderers
    - A simple "Export" button in Unity's SpritePacker window (I guess this really shouldn't be too hard and would make Unity's atlas system again a bit more flexible)
    Last edited: Aug 17, 2015
    Kiori likes this.
  12. Kiori


    Jun 25, 2014
    You should post any script that has the potential to help the community.
  13. WiedemannD


    Mar 4, 2015
    Well then here it comes:

    Sprite Packer Export Tool

    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEditor;
    3. using UnityEditor.Sprites;
    4. using System.Collections;
    5. using System.Collections.Generic;
    6. using System.IO;
    7. public class FTSpritePackerExportWindow : EditorWindow
    8. {
    9.     // VO to cache sprite data in
    10.     private struct FTSpritePackerSpriteData
    11.     {
    12.         public string texturePath;
    13.         public Sprite sprite;
    14.         public SpriteMetaData spriteMetaData;
    15.     }
    16.     // VO to cache atlas data in
    17.     private class FTSpritePackerAtlasData
    18.     {
    19.         public string atlasName;
    20.         public Texture2D unityAtlasTexture;
    21.         public string atlasTextureAssetPath;
    22.         public string atlasTextureFilePath;
    23.         public bool settingsCached = false;
    24.         public bool mipmapEnabled;
    25.         public int compressionQuality;
    26.         public TextureImporterFormat textureFormat;
    27.         public FilterMode filterMode;
    28.         public float pixelsPerUnit;
    29.         public List<FTSpritePackerSpriteData> spritesData = new List<FTSpritePackerSpriteData>();
    30.     }
    31.     // VO to cache texture, textureImporter and textureImporter settings
    32.     private struct FTSpritePackerTextureData
    33.     {
    34.         public string texturePath;
    35.         public Texture2D texture;
    36.         public TextureImporter textureImporter;
    37.         public bool isReadable;
    38.         public TextureImporterFormat textureFormat;
    39.     }
    40.     // menu entries
    41.     [MenuItem("FT/Window/Sprite Packer Export")]
    42.     [MenuItem("Window/FT Sprite Packer Export")]
    43.     public static void ShowWindow()
    44.     {
    45.         EditorWindow.GetWindow(typeof(FTSpritePackerExportWindow));
    46.     }
    47.     public static FTSpritePackerExportWindow exporter;
    48.     private static string spritesPath;
    49.     private static string spriteAtlasesDir;
    50.     private static bool forceSquareAtlasExport = true;
    51.     private static bool debugLogMessages = true;
    52.     private static List<string> atlasesToExport = new List<string>();
    53.     private static List<FTSpritePackerTextureData> readableOriginalTexturesData;
    54.     private static List<FTSpritePackerAtlasData> atlasesData;
    55.     private static Color[] transparentPixelColors4096 = new Color[4096 * 4096];
    56.     private static Vector2 scrollPosition =;
    57.     private static float exportProgress = 1.0f;
    58.     private static string exportCurrentAtlasPath = "";
    59.     private static System.DateTime exportStartDateTime;
    60.     // load settings from EditorPrefs or defaults
    61.     private void loadWindowSettings()
    62.     {
    63.         spritesPath = EditorPrefs.GetString("spritesPath", "Assets/Sprites");
    64.         spriteAtlasesDir = EditorPrefs.GetString("spriteAtlasesDir", "SpriteAtlases");
    65.         forceSquareAtlasExport = EditorPrefs.GetBool("forceSquareAtlasExport", true);
    66.         debugLogMessages = EditorPrefs.GetBool("debugLogMessages", true);
    67.         // load possible sprite atlas export settings
    68.         foreach(string atlasName in Packer.atlasNames)
    69.         {
    70.             if(EditorPrefs.GetBool("spriteAtlasExport_" + atlasName, false) && !atlasesToExport.Contains(atlasName))
    71.             {
    72.                 atlasesToExport.Add(atlasName);
    73.             }
    74.         }
    75.     }
    77.     // saves settings to EditorPrefs
    78.     private void saveWindowSettings()
    79.     {
    80.         EditorPrefs.SetString("spritesPath", spritesPath);
    81.         EditorPrefs.SetString("spriteAtlasesDir", spriteAtlasesDir);
    82.         EditorPrefs.SetBool("forceSquareAtlasExport", forceSquareAtlasExport);
    83.         EditorPrefs.SetBool("debugLogMessages", debugLogMessages);
    84.         // save possible sprite atlas export settings
    85.         foreach(string atlasName in atlasesToExport)
    86.         {
    87.             EditorPrefs.SetBool("spriteAtlasExport_" + atlasName, true);
    88.         }
    89.     }
    91.     void OnEnable()
    92.     {
    93.         exporter = this;
    94.         loadWindowSettings();
    95.         // load transparent pixel color array
    96.         Color transparentPixelColor = new Color(0f,0f,0f,0f);
    97.         for(int i = 0; i < transparentPixelColors4096.Length; i++)
    98.         {
    99.             transparentPixelColors4096[i] = transparentPixelColor;
    100.         }
    101.     }
    103.     void OnGUI()
    104.     {
    105.         //FTEditorDrawingUtils.createSpace();
    106.         // settings
    107.         EditorGUI.BeginChangeCheck();
    108.         spritesPath = EditorGUILayout.TextField(new GUIContent("Sprites Search Path:", "The path and its subfolders where all sprites are stored. This speeds up the export process. Leave empty when all folders should be searched through."), spritesPath);
    109.         spriteAtlasesDir = EditorGUILayout.TextField(new GUIContent("Sprite Atlases Dir: Assets/", "The subfolder in Assets where sprite atlases will be stored."), spriteAtlasesDir);
    110.         forceSquareAtlasExport = EditorGUILayout.Toggle(new GUIContent("Force square atlas export", "This ads extra transparent pixels to conform to POT square texture compression like PVRTC. Textures will store more pixel data, but might still need less memory through compression."), forceSquareAtlasExport);
    111.         debugLogMessages = EditorGUILayout.Toggle(new GUIContent("Log", "Logs some messages"), debugLogMessages);
    112.         if(EditorGUI.EndChangeCheck()) saveWindowSettings();
    113.         // controls
    114.         EditorGUILayout.BeginHorizontal();
    115.         if(GUILayout.Button(new GUIContent("Refresh/Rebuild Atlas Cache", "Rebuilds sprite atlas cache if needed. Mostly a good idea to trigger before exporting.")))
    116.         {
    117.             Packer.RebuildAtlasCacheIfNeeded(EditorUserBuildSettings.selectedStandaloneTarget, true);
    118.             loadWindowSettings();
    119.         }
    120.         if(GUILayout.Button(new GUIContent("Export Selected Atlases", "Creates png files of all pages of selected atlases and assignes sprite meta data to them."))) exportSelectedAtlases();
    121.         EditorGUILayout.EndHorizontal();
    122.         // list all cached atlases
    123.         //FTEditorDrawingUtils.drawLabel("Cached Sprite Atlases", "Check atlas to add to export queue.", FT.LabelStyleTemplate.BOLD_AND_INVERTED, true);
    124.         EditorGUILayout.LabelField(new GUIContent("Cached Sprite Atlases", "Check atlas to add to export queue."));
    125.         scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition);
    126.         if(Packer.atlasNames.Length == 0)
    127.         {
    128.             //FTEditorDrawingUtils.drawLabel("Hit Refresh/Rebuild Atlas Cache, when configured atlases are not visble here.");
    129.             EditorGUILayout.LabelField(new GUIContent("Hit Refresh/Rebuild Atlas Cache, when configured atlases are not visble here."));
    130.         }
    131.         foreach(string atlasName in Packer.atlasNames)
    132.         {
    133.             bool exportAtlas = (atlasesToExport.Contains(atlasName)) ? true : false;
    134.             Texture2D[] textures = Packer.GetTexturesForAtlas(atlasName);
    135.             // draw editor toggle
    136.             bool exportAtlasNew = EditorGUILayout.Toggle(new GUIContent(atlasName, textures.Length + " pages"), exportAtlas);
    137.             // add or remove from atlasesToExport list
    138.             if(exportAtlasNew != exportAtlas)
    139.             {
    140.                 if(exportAtlasNew)
    141.                 {
    142.                     atlasesToExport.Add(atlasName);
    143.                     EditorPrefs.SetBool("spriteAtlasExport_" + atlasName, true);
    144.                 }
    145.                 else
    146.                 {
    147.                     atlasesToExport.Remove(atlasName);
    148.                     EditorPrefs.DeleteKey("spriteAtlasExport_" + atlasName);
    149.                 }
    150.             }
    151.         }
    152.         EditorGUILayout.EndScrollView();
    153.         // show progress bar when performing export
    154.         if(exportProgress < 1.0f)
    155.         {
    156.             EditorUtility.DisplayProgressBar("Exporting Sprite Atlases", "Exporting to " + exportCurrentAtlasPath + ".", exportProgress);
    157.         }
    158.         else
    159.         {
    160.             EditorUtility.ClearProgressBar();
    161.         }
    162.     }
    163.     void OnInspectorUpdate()
    164.     {
    165.         Repaint();
    166.     }
    167.     public static void exportSelectedAtlases()
    168.     {
    169.         // log export time span
    170.         exportStartDateTime = System.DateTime.Now;
    171.         // some dictionaries/lists for caching/look up
    172.         readableOriginalTexturesData = new List<FTSpritePackerTextureData>();
    173.         atlasesData = new List<FTSpritePackerAtlasData>();
    174.         // check if export path exists and create it if needed
    175.         if(!AssetDatabase.IsValidFolder("Assets/" + spriteAtlasesDir))
    176.         {
    177.             AssetDatabase.CreateFolder("Assets", spriteAtlasesDir);
    178.             debugLog("Sprite Atlases path Assets/" + spriteAtlasesDir + " was created.");
    179.         }
    180.         EditorUtility.DisplayProgressBar("Exporting Sprite Atlases", "Exporting to Assets/" + spriteAtlasesDir + ". This may take a while ...", exportProgress);
    181.         try
    182.         {
    183.             // cache relevant unity atlases/pages and their export paths
    184.             foreach(string atlasToExport in atlasesToExport)
    185.             {
    186.                 Texture2D[] atlasToExportTextures = Packer.GetTexturesForAtlas(atlasToExport);
    187.                 for(int i = 0; i < atlasToExportTextures.Length; i++)
    188.                 {
    189.                     Texture2D atlasToExportTexture = atlasToExportTextures[i];
    190.                     FTSpritePackerAtlasData atlasData = new FTSpritePackerAtlasData();
    191.                     atlasData.atlasName = atlasToExport;
    192.                     atlasData.unityAtlasTexture = atlasToExportTexture;
    193.                     // paginate filename
    194.                     if(atlasToExportTextures.Length > 1)
    195.                     {
    196.                         atlasData.atlasTextureAssetPath = spriteAtlasesDir + "/" + atlasToExport + ".Page" + (i + 1) + ".png";
    197.                     }
    198.                     else
    199.                     {
    200.                         atlasData.atlasTextureAssetPath = spriteAtlasesDir + "/" + atlasToExport + ".png";
    201.                     }
    202.                     atlasData.atlasTextureFilePath = Application.dataPath + "/" + atlasData.atlasTextureAssetPath;
    203.                     atlasesData.Add(atlasData);
    204.                 }
    205.             }
    206.             // iterate over all textures and collect refs for relevant ones
    207.             string[] textureGUIDs;
    208.             if(spritesPath == "")
    209.             {
    210.                 textureGUIDs = AssetDatabase.FindAssets("t:Texture");
    211.             }
    212.             else
    213.             {
    214.                 textureGUIDs = AssetDatabase.FindAssets("t:Texture", new string[]{spritesPath});
    215.             }
    216.             // iterate over all textures found
    217.             foreach(string textureGUID in textureGUIDs)
    218.             {
    219.                 string textureAssetPath = AssetDatabase.GUIDToAssetPath(textureGUID);
    220.                 TextureImporter textureImporter = AssetImporter.GetAtPath(textureAssetPath) as TextureImporter;
    221.                 // check if textureImporter is available
    222.                 if (textureImporter != null)
    223.                 {
    224.                     string atlasName = textureImporter.spritePackingTag;
    225.                     // check if spritePackingTag is among the selected relevant ones
    226.                     if(!string.IsNullOrEmpty(atlasName) && (atlasesToExport.Contains(atlasName) || isGroupedAtlasToExport(atlasName)))
    227.                     {
    228.                         string texturePath = AssetDatabase.GUIDToAssetPath(textureGUID);
    229.                         Object[] textureObjects = AssetDatabase.LoadAllAssetRepresentationsAtPath(texturePath);
    230.                         List<SpriteMetaData> textureImporterSpritesMetaData = new List<SpriteMetaData>();
    231.                         textureImporterSpritesMetaData.AddRange(textureImporter.spritesheet);
    232.                         // iterate over main and subassets, check if relevant sprites are present and store them in atlasesToExportSprites
    233.                         foreach(Object textureObject in textureObjects)
    234.                         {
    235.                             // only go over sprites
    236.                             if(textureObject is Sprite)
    237.                             {
    238.                                 Sprite textureSprite = textureObject as Sprite;
    239.                                 Texture2D textureSpriteAtlasTexture = SpriteUtility.GetSpriteTexture(textureSprite, true);
    240.                                 // add sprite data (metaData, rect, sprite) to corresponding atlasData
    241.                                 foreach(FTSpritePackerAtlasData atlasData in atlasesData)
    242.                                 {
    243.                                     if(textureSpriteAtlasTexture == atlasData.unityAtlasTexture)
    244.                                     {
    245.                                         // cache new atlas textureImporter settings according to first sprite that belongs to atlas page
    246.                                         if(!atlasData.settingsCached)
    247.                                         {
    248.                                             atlasData.compressionQuality = textureImporter.compressionQuality;
    249.                                             atlasData.filterMode = textureImporter.filterMode;
    250.                                             atlasData.mipmapEnabled = textureImporter.mipmapEnabled;
    251.                                             atlasData.pixelsPerUnit = textureImporter.spritePixelsPerUnit;
    252.                                             atlasData.textureFormat = textureImporter.textureFormat;
    253.                                             atlasData.settingsCached = true;
    254.                                         }
    255.                                         // cache sprite meta data
    256.                                         FTSpritePackerSpriteData spriteData = new FTSpritePackerSpriteData();
    257.                                         spriteData.sprite = textureSprite;
    258.                                         spriteData.texturePath = texturePath;
    259.                                         Vector2[] uvs = SpriteUtility.GetSpriteUVs(textureSprite, true);
    260.                                         Rect spriteRect = getRectFromUVs(uvs, textureSpriteAtlasTexture.width, textureSpriteAtlasTexture.height);
    261.                                         // TODO: custom border and pivot export are less than exact! Might wanna do something about it, if it's needed.
    262.                                         spriteData.spriteMetaData = new SpriteMetaData();
    263.                                         spriteData.spriteMetaData.border = textureSprite.border;
    264.                                =;
    265.                                         spriteData.spriteMetaData.rect = spriteRect;
    266.                                         // single sprite textures
    267.                                         if(textureImporterSpritesMetaData.Count == 0)
    268.                                         {
    269.                                             spriteData.spriteMetaData.alignment = (textureImporter.spritePivot.x == 0.5f && textureImporter.spritePivot.y == 0.5f) ? (int) SpriteAlignment.Center : (int) SpriteAlignment.Custom;
    270.                                             spriteData.spriteMetaData.pivot = textureImporter.spritePivot;
    271.                                         }
    272.                                         // multiple sprite textures
    273.                                         else
    274.                                         {
    275.                                             // retrieve alignment and pivot from importerSpriteMetaData
    276.                                             foreach(SpriteMetaData textureImporterSpriteMetaData in textureImporterSpritesMetaData)
    277.                                             {
    278.                                                 if( ==
    279.                                                 {
    280.                                                     spriteData.spriteMetaData.alignment = textureImporterSpriteMetaData.alignment;
    281.                                                     spriteData.spriteMetaData.pivot = textureImporterSpriteMetaData.pivot;
    282.                                                     textureImporterSpritesMetaData.Remove(textureImporterSpriteMetaData);
    283.                                                     break;
    284.                                                 }
    285.                                             }
    286.                                         }
    287.                                         atlasData.spritesData.Add(spriteData);
    288.                                         break;
    289.                                     }
    290.                                 }
    291.                             }
    292.                         }
    293.                         // cache original textureImporter settings and set it readable etc. and store it in list
    294.                         FTSpritePackerTextureData originalTextureData = new FTSpritePackerTextureData();
    295.                         originalTextureData.textureImporter = textureImporter;
    296.                         originalTextureData.isReadable = textureImporter.isReadable;
    297.                         originalTextureData.textureFormat = textureImporter.textureFormat;
    298.                         originalTextureData.texturePath = textureAssetPath;
    299.                         // set temporary textureImporter settings
    300.                         textureImporter.textureFormat = TextureImporterFormat.ARGB32;
    301.                         textureImporter.isReadable = true;
    302.                         // apply temporary settings
    303.                         AssetDatabase.ImportAsset(textureAssetPath);
    305.                         originalTextureData.texture = (Texture2D) AssetDatabase.LoadAssetAtPath(textureAssetPath, typeof(Texture2D));
    306.                         // store in list
    307.                         readableOriginalTexturesData.Add(originalTextureData);
    308.                     }
    309.                 }
    310.             }
    311.             // recreate/export png files from unity atlas pages
    312.             foreach(FTSpritePackerAtlasData atlasData in atlasesData)
    313.             {
    314.                 // check if atlasData actually has cached sprites, if not skip.
    315.                 if(atlasData.spritesData.Count == 0)
    316.                 {
    317.                     debugLog("Sprites for atlas " + atlasData.atlasName + " could not be found. Maybe adjust Sprites Search Path?", true);
    318.                     continue;
    319.                 }
    320.                 exportAtlas(atlasData);
    321.             }
    322.             // refresh asset db to auto create TextureImporters for newly exported atlases
    323.             AssetDatabase.Refresh();
    324.             // apply atlas sprite data and settings to newly exported atlases
    325.             foreach(FTSpritePackerAtlasData atlasData in atlasesData)
    326.             {
    327.                 // check if atlasData actually has cached sprites, if not skip.
    328.                 if(atlasData.spritesData.Count == 0)
    329.                 {
    330.                     continue;
    331.                 }
    332.                 TextureImporter exportedAtlasTextureImporter = (TextureImporter) TextureImporter.GetAtPath("Assets/" + atlasData.atlasTextureAssetPath);
    333.                 exportedAtlasTextureImporter.spriteImportMode = SpriteImportMode.Multiple;
    334.                 exportedAtlasTextureImporter.maxTextureSize = (atlasData.unityAtlasTexture.width > atlasData.unityAtlasTexture.height) ? atlasData.unityAtlasTexture.width : atlasData.unityAtlasTexture.height;
    335.                 exportedAtlasTextureImporter.compressionQuality = atlasData.compressionQuality;  
    336.                 exportedAtlasTextureImporter.filterMode = atlasData.filterMode;
    337.                 exportedAtlasTextureImporter.mipmapEnabled = atlasData.mipmapEnabled;
    338.                 exportedAtlasTextureImporter.spritePixelsPerUnit = atlasData.pixelsPerUnit;
    339.                 exportedAtlasTextureImporter.textureFormat = atlasData.textureFormat;
    340.                 SpriteMetaData[] spritesMetaData = new SpriteMetaData[atlasData.spritesData.Count];
    341.                 for(int i = 0; i < atlasData.spritesData.Count; i++)
    342.                 {
    343.                     spritesMetaData[i] = atlasData.spritesData[i].spriteMetaData;
    344.                 }
    345.                 exportedAtlasTextureImporter.spritesheet = spritesMetaData;
    346.                 // apply new settings
    347.                 AssetDatabase.ImportAsset("Assets/" + atlasData.atlasTextureAssetPath);
    348.             }
    349.             // reset settings of original textures
    350.             foreach(FTSpritePackerTextureData textureData in readableOriginalTexturesData)
    351.             {
    352.                 textureData.textureImporter.textureFormat = textureData.textureFormat;
    353.                 textureData.textureImporter.isReadable = textureData.isReadable;
    354.                 // apply cached settings again
    355.                 AssetDatabase.ImportAsset(textureData.texturePath);
    356.             }
    357.             AssetDatabase.Refresh();
    358.             // clear caching lists
    359.             readableOriginalTexturesData.Clear();
    360.             atlasesData.Clear();
    361.             // progressbar
    362.             exportProgress = 1.0f;
    363.             //exporter.Repaint();
    364.             // some logging
    365.             System.TimeSpan exportTimeSpan = System.DateTime.Now.Subtract(exportStartDateTime);
    366.             debugLog("Exported atlases in " + exportTimeSpan.TotalSeconds + " seconds.");
    367.             exportStartDateTime = System.DateTime.Now;
    368.         }
    369.         catch(System.Exception e)
    370.         {
    371.             Debug.LogError(e.Message + "\n" + e.StackTrace);
    372.             exportProgress = 1.0f;
    373.             EditorUtility.ClearProgressBar();
    374.         }
    375.     }
    376.     // export single atlas page from atlasData
    377.     private static void exportAtlas(FTSpritePackerAtlasData atlasData)
    378.     {
    379.         // create temporary texture to fill
    380.         Texture2D atlasTextureToExport = null;
    381.         if(!forceSquareAtlasExport || atlasData.unityAtlasTexture.width == atlasData.unityAtlasTexture.height)
    382.         {
    383.             atlasTextureToExport = new Texture2D(atlasData.unityAtlasTexture.width, atlasData.unityAtlasTexture.height, TextureFormat.ARGB32, false);
    384.         }
    385.         // force square texture export
    386.         else
    387.         {
    388.             int squareAtlasSize = (atlasData.unityAtlasTexture.width > atlasData.unityAtlasTexture.height) ? atlasData.unityAtlasTexture.width : atlasData.unityAtlasTexture.height;
    389.             atlasTextureToExport = new Texture2D(squareAtlasSize, squareAtlasSize, TextureFormat.ARGB32, false);
    390.         }
    391.         atlasTextureToExport.SetPixels(transparentPixelColors4096);
    392.         // TODO: progress bar does not seem to actually show the progress ...
    393.         // set progressbar vars
    394.         exportCurrentAtlasPath = atlasData.atlasTextureAssetPath;
    395.         //exporter.Repaint();
    396.         foreach(FTSpritePackerSpriteData spriteData in atlasData.spritesData)
    397.         {
    398.             Sprite atlasSprite = spriteData.sprite;
    399.             Texture2D originalTexture = null;
    400.             foreach(FTSpritePackerTextureData readableOriginalTextureData in readableOriginalTexturesData)
    401.             {
    402.                 if(readableOriginalTextureData.texturePath == spriteData.texturePath)
    403.                 {
    404.                     originalTexture = readableOriginalTextureData.texture;
    405.                     break;
    406.                 }
    407.             }
    408.             // get sprite pixels from original texture
    409.             Rect originalSpriteRect = getRectFromUVs(atlasSprite.uv, originalTexture.width, originalTexture.height);
    410.             // set ints spriteRectWidth/Height to avoid mismatching conversions, this also seems to solve missing pixels issues for whatever reason
    411.             int spriteRectWidth = (int) originalSpriteRect.width;
    412.             int spriteRectHeight = (int) originalSpriteRect.height;
    413.             Color[] spritePixels = originalTexture.GetPixels((int) originalSpriteRect.x, (int) originalSpriteRect.y, spriteRectWidth, spriteRectHeight);
    414.             // set pixels
    415.             atlasTextureToExport.SetPixels((int) spriteData.spriteMetaData.rect.x, (int) spriteData.spriteMetaData.rect.y, spriteRectWidth, spriteRectHeight, spritePixels);
    416.         }
    417.         atlasTextureToExport.Apply();
    418.         // delete previously exported atlas with same file path to avoid Unity's strange asset meta data caching/import behaviour
    419.         AssetDatabase.DeleteAsset("Assets/" + atlasData.atlasTextureAssetPath);
    420.         // create exported atlas file
    421.         File.WriteAllBytes(atlasData.atlasTextureFilePath, atlasTextureToExport.EncodeToPNG());
    422.         debugLog("Created/Updated sprite atlas file Assets/" + atlasData.atlasTextureAssetPath + ".");
    423.     }
    424.     // checks if atlas name + " (Group " is one from atlasesToExport
    425.     private static bool isGroupedAtlasToExport(string atlasName)
    426.     {
    427.         foreach(string atlasToExport in atlasesToExport)
    428.         {
    429.             if(atlasToExport.StartsWith(atlasName + " (Group ")) return true;
    430.         }
    431.         return false;
    432.     }
    433.     // returns corresponding rect from uvs
    434.     private static Rect getRectFromUVs(Vector2[] uvs, int textureWidth, int textureHeight)
    435.     {
    436.         Rect rect = new Rect(1, 1, 0, 0);
    437.         // get uv coordinates for sprite on atlas texture
    438.         foreach (Vector2 uv in uvs)
    439.         {
    440.             if(uv.x < rect.x) rect.x = uv.x;
    441.             if(uv.y < rect.y) rect.y = uv.y;
    442.             if(uv.x > rect.width) rect.width = uv.x;
    443.             if(uv.y > rect.height) rect.height = uv.y;
    444.         }
    446.         // convert coordinates to pixel coordinates
    447.         rect.width = (rect.width - rect.x) * textureWidth;
    448.         rect.height = (rect.height - rect.y) * textureHeight;
    449.         rect.x *= textureWidth;
    450.         rect.y *= textureHeight;
    451.         // round to int properly
    452.         rect.width = Mathf.RoundToInt(rect.width);
    453.         rect.height = Mathf.RoundToInt(rect.height);
    454.         rect.x = Mathf.RoundToInt(rect.x);
    455.         rect.y = Mathf.RoundToInt(rect.y);
    456.         // make sure position is not negative
    457.         if(rect.x < 0) rect.x = 0;
    458.         if(rect.y < 0) rect.y = 0;
    459.         return rect;
    460.     }
    461.     // debug log
    462.     private static void debugLog(string msg, bool warning = false)
    463.     {
    464.         if(debugLogMessages)
    465.         {
    466.             if(warning)
    467.             {
    468.                 Debug.LogWarning("FTSpritePackerExportWindow: " + msg);
    469.             }
    470.             else
    471.             {
    472.                 Debug.Log("FTSpritePackerExportWindow: " + msg);
    473.             }
    474.         }
    475.     }
    476. }
    Again, thanks to tmsduman's help and
    ortin and Kiori like this.
  14. jamez


    Jun 17, 2014
    @ColossalPaul I've been playing with your project - it's helpful in figuring this out. However I'm getting some weird problems on my textures.

    As a simple example that is easy to reproduce, I just added the default Particle System to your Variant project. When run from the boostrap scene, the particles are missing their texture (particles are drawn as magenta squares). If I select the Particle System while the game is is running, go to the inspector, and set the shader to "Particles/Alpha Blended Premultiply" (which it is already set to, I'm just setting it again) it start working again. What is going on here? I'm having similar problems for other meshes I have drawing in my game.

    Attached example project, this is just your Variant project with Particle System added in.

    Attached Files:

  15. seejaneworkit


    Mar 20, 2013
  16. impulse9


    Sep 17, 2015
    Is there a way to force downloading new version using LoadFromCacheOrDownload without manually setting version parameter?
    Last edited: Sep 18, 2015
  17. jamez


    Jun 17, 2014
    I've just been doing this before LoadFromCacheOrDownload:
    Code (CSharp):
    1. Caching.CleanCache(); // force reloading assets
    Seems to solve the version parameter problem for me... but maybe I don't know what I'm talking about because I can't actually get the variant loading to work properly... seems pretty much completely broken to me.
  18. impulse9


    Sep 17, 2015
    What precisely doesn't work for you? It seems to work fine on my end, I have a VariantLoader script:
    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    4. public class VariantLoader : MonoBehaviour
    5. {
    7.     static string basePath = "file://" +  System.Environment.CurrentDirectory.Replace("\\", "/") + "/Assets/AllMyBundles/";
    9.     public string assets = "hd";
    10.     public int version = 1;
    11.     private static bool bootstrap = false;
    12.     private static int currentVersion = 0;
    13.     private static VariantLoader instance;
    15.     // Use this for initialization
    16.     IEnumerator Start ()
    17.     {
    18.         instance = this;
    19.         DontDestroyOnLoad(gameObject);
    20.         bootstrap = true;
    21.         currentVersion = version;
    22.         yield return WWW.LoadFromCacheOrDownload(basePath + "stuff." + assets, version);
    23.         LoadLevel("test");
    24.     }
    25.     public static void LoadLevel(string id)
    26.     {
    27.         instance.StartCoroutine(instance.ILoad(id));
    28.     }
    29.     public IEnumerator ILoad(string id)
    30.     {
    31.         if(bootstrap)
    32.             yield return WWW.LoadFromCacheOrDownload(basePath + id + ".unity", currentVersion);
    33.         Application.LoadLevel (id);
    34.     }
    35. }
    and BuildAssetBundles
    Code (CSharp):
    1. using UnityEditor;
    2. using System.Collections;
    4. public static class BuildAssetBundles
    5. {
    6.     [MenuItem("My Asset Bundles/Build It")]
    7.     static void BuildIt ()
    8.     {
    9.         BuildPipeline.BuildAssetBundles("Assets/AllMyBundles");
    10.     }
    11. }
    Then I have sd and hd folders labeled like this:

    (stuff/sd for sd folder)

    and test scene like this:

    Also I have a bootstrap scene with VariantLoader script attached to a single empty game object.

    And it works just fine after building the bundles :) When I set assets to "hd" my test scene loads and uses bitmaps from the hd folder. If I set it to "sd" it loads sd ones. Particle system seems to work fine as well.

    I've just downloaded your example with particle system and it didn't load the texture, however after changing
    Code (CSharp):
    1. BuildPipeline.BuildAssetBundles("Assets/AllMyBundles", BuildAssetBundleOptions.None, BuildTarget.StandaloneOSXUniversal);
    Code (CSharp):
    1. BuildPipeline.BuildAssetBundles("Assets/AllMyBundles");
    it started working for me.

    Attached Files:

    Last edited: Sep 20, 2015
  19. jamez


    Jun 17, 2014
    @impulse9 Thanks, my 2d sprites are working fine between SD and HD, but I'm having issues with my mesh renderers and particle system. They don't show up at all - something weird is happening with the shaders. I posted a very basic example project in a previous post, which just takes the variant project and adds a particle system to it - the particles don't show up at all until I manually reassign the shader. I'm curious if that project works for other people.
  20. impulse9


    Sep 17, 2015
    On mac it works fine, on Windows I had to change BuildAssetBundles parameters from
    Code (CSharp):
    1. BuildPipeline.BuildAssetBundles("Assets/AllMyBundles", BuildAssetBundleOptions.None, BuildTarget.StandaloneOSXUniversal);
    Code (CSharp):
    1. BuildPipeline.BuildAssetBundles("Assets/AllMyBundles");
    I guess it defaults to the current operating system, so setting BuildTarget to StandaloneWindows64 would work as well.
  21. jamez


    Jun 17, 2014
    Awesome, thanks so much, I was getting pretty frustrated with this!
    I did not realize that the assets were OS specific. So I guess that we have to build separate assets for 32 bit and 64 bit Windows?
  22. Kiori


    Jun 25, 2014
  23. Kiori


    Jun 25, 2014
  24. andsee


    Mar 20, 2012
    To summarise the below. Please give us an option to scale the created sprite atlas texture as part of a custom sprite packer policy. Ideally also give us an option to tick a box and halve all textures in a build, this may solve the problem for many apps and those that need more control can use the setting to easily create asset bundles with half size textures, without requiring us to maintain a copy of all textures at half resolution. If you want to be extra kind then allow us to specify the scaling algorithm used, but personally the same as the mipmap generation works for me.

    Asset bundles are great for more complex use cases. However I have my app and it handles all the different aspect ratios etc using 9 slicing, anchors etc. There's also 3D textures cameras all the stuff you expect. The app and art assets are designed for high resolution devices, there are enough texels for such displays.

    Now I want to create a build for a lower resolution device, tvOS or perhaps I want to create an asset bundle to allow switching between resolutions as outlined above. Since I know the device has less than half the screen pixels than the resolution the app is designed for I know I can halve ALL textures across the whole app and see no visual difference.

    I do not want to have to keep two copies of the texture in the project, if an artist modifies the texture it's very unlikely that they will do anything other than a photoshop scale operation to generate the other.

    Now for many of my textures I can halve the size by modifying the maximum size in the texture import settings (either when switching builds or even perhaps between creating asset bundles) but for sprites being packed by Unity I can not do this.

    I'm using a custom sprite packer policy already and it seems to me that all I need is the ability to scale the final packed texture. Note that the app is already designed so that things are padded and work at half and quarter size without images interfering with their neighbours.
  25. AAverin


    Nov 7, 2015
    Hey guys!

    I see that this thread is pretty fresh yet, and was pretty live throughout the whole year. I've read most of it, but looks like there lots of misunderstandings and attempts to figure out how things to really work in the background.

    I tried visiting that live session link with tutorial but it give me 404

    @ColossalPaul and other Unity officials.
    Guys, is there some kind of final solution and final tutorial/lesson/blog-post that explains how to make your game grab different assets depending on resolution of device? Do I really need to read all this 1 year long conversation to figure things out?
  26. madclouds


    Nov 6, 2013
    @ColossalPaul or @Mike Geig - Any update on this? It's been nearly 5 months since your last update to this thread.
  27. deemen


    Dec 29, 2013
    I haven't been following the thread closely, but in the last 6 months, I can tell you what my team did:
    • We ditched asset bundles and asset bundle variants completely. Unless someone can convince me there has been a lot of progress there, we probably won't be using them any time soon. They are very difficult to get working correctly and it's a huge black box.
    • We used tk2d to PNG compress and pick the right resolution for our assets. This is built into 2D Toolkit and works reasonably well.
    • Objects that use sprite renderers with animators like characters all had mip maps turned on with Trilinear filtering which seems to give acceptable image quality. This gave us a 33% memory penalty, but it had to be done.
    • For the cutscenes in our game we wrote a special component to stream the textures in as the frames became visible and it also chose the right one based on resolution. This is basically run-time loading of the resources based on a path. You lose all the benefits of proper asset links, but with the right tooling it can be made functional.
    Basically we had to rely on a few crafty solutions instead of something built in. Our game works now, but yeah, it's not great.

    @ColossalPaul you asked me months ago if I could provide a repro case for Unity Asset Bundles giving hash collisions. Unfortunately I can't do share anything on the forums without breaking the NDA with our client. If you'd like me to provide some more info please PM.
  28. Mike-Geig


    Unity Technologies

    Aug 16, 2013
    I'm not sure what the question is. I thought this discussion was more or less resolved with the assetbundle manager information (posted above by Kiori). Is there some more specific information needed?

    @deemen, what about asset bundle variants didn't work for you? Did you try the manager? Was there a bug or anything?
  29. deemen


    Dec 29, 2013
    I'll have to try the manager with our next title, the one we were working on back in July is past the point of a tech change like this. I will report back. Thanks for pointing out the link!
  30. bryantdrewjones


    Aug 29, 2011
    @ColossalPaul & @Mike Geig,

    I need to load different sets of sprites depending on the platform and/or device resolution. Asset bundles appear to be the solution, so I've been playing around with the AssetBundleManager. But my impression is that it's taking an already over-complicated system and making it even more confusing. And I still haven't seen any examples or documentation that describe a full start-to-end workflow for simple use cases.

    For example, where is the sample/documentation that shows how to include asset bundles in builds and load them with the correct file path? I'm assuming I have to make a custom build post processor that copies the platform's asset bundles somewhere? And then I do a file:// request to that path at runtime? Seems like the most basic use case, but I feel like I'm taking stabs in the dark trying to figure out how these systems all work together :(

    As another example, not being able to test asset bundle variants in the editor when using Simulation Mode is a huge limitation when all I want to do is check if my assets are loading and displaying correctly at multiple resolutions.

    On Demand Resources looks like another huge can of worms :eek: The ReadMe on BitBucket mentions that the Manager handles it all for you, but the sample project included with the Manager shows some extra steps involved that fall to the user. Do I have to build and load the ODRs separately from the asset bundles? Are they the same thing?

    In chatting with some other Unity devs, I get the feeling that Unity views 2D multi-resolution support as a solved problem. And it does sound like all the technical pieces are in place to support it. But the documentation is still frustratingly lacking and/or over-complicated.

    A tutorial or blogpost that goes beyond simply building asset bundles and shows the full workflow for dealing with the bundles at runtime outside of the editor on multiple platforms would be much appreciated :)
  31. ColossalPaul


    Unity Technologies

    May 1, 2013
    Hi Guys,

    Thanks for keeping this alive. So having some time pass and yet this remains a problem means we really have an issue on our hands. So let's deal with it.

    For those who has tried and given up please go here to help us understand why:

    For those who hasn't tried it. Here is a completely stripped down example to illustrate how it could work. Please try it out, and if you figure it doesn't work for you, please join the other thread and share your experience on how it failed.


  32. coshea


    Dec 20, 2012
    For years I have used toolkit2d, you create your sprites and just add a new platform for 2x, it automatically create two atlas sheets. Then with one line of code I can change toolkit2d to use 1x or 2x size textures for sprites.

    I'm considering moving to Unity sprites long term, I like the polygon packing, BUT I can't find any concrete this is definitely how you should do HD/SD sprite atlas with Unity sprites. I know this thread is 3 years old, but can someone let me know the best/easiest way to do it now?

    I fear if I have to use AssetBundles I'll just stick with toolkit2d as the simplicity isn't worth the tradeoff.

  33. Remiel


    Oct 17, 2012
    In case anyone else stumbles onto this thread in the future with the same question...

    Since Unity 2017.1, Unity has Variant Sprite Atlases that can be used to solve this issue. The main idea behind this is to create a main sprite atlas where we will add folders and sprites we want to include in the atlas. Then, we create a variant atlas whose main atlas is the previously mentioned atlas. The variant atlas will be identical to the main atlas, containing the same sprites, but you will be able to adjust the scale to create a smaller resolution.

    So basically, you would add HD sprites to your unity project, which will be added to the HD atlas. Then you'd create a variant SD atlas with scale of eg. 0.5, whose main atlas is the HD atlas.
    Then the only thing you have to do is to change which atlas you are including in the build when you build the game. If you are building for HD devices, you'd check the "include in build" checkbox on the HD atlas, and uncheck it on the variant. Or vice versa if you are building for SD devices.

    Now, if you want to include both a HD and SD version in your build to change resolution at runtime, then things get more complicated.

    One method that is worth trying out is to place both SD & HD (variant and master) atlas in Resources folder and uncheck "include in build" for both. Then in a bootstrap scene, detect the resolution of your device, and via script load one of the atlases from resources. That way, when Unity requests for a certain sprite, it will take it from the loaded atlas. That should, in theory, display the right image. However, I haven't tried this out personally, so I can't vouch that it would work as expected.
  34. AlekseyLaz


    Aug 16, 2016
    Did you find a solution?