Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.

TextMesh Pro Text Mesh Pro does not work with spriteatlas assets.

Discussion in 'UGUI & TextMesh Pro' started by sidespin, Jun 18, 2019.

  1. sidespin

    sidespin

    Joined:
    Jun 22, 2018
    Posts:
    20
    When I try to create a Text Mesh Pro Sprite Assets it says "A texture which contains sprites must first be selected in order to create a TextMesh Pro Sprite Asset."

    What's the proper way to use the spriteatlas file I already have?
     
    suiboli314 and Laicasaane like this.
  2. Stephan_B

    Stephan_B

    Unity Technologies

    Joined:
    Feb 26, 2017
    Posts:
    6,582
    The Atlas Texture must first be set to Sprite Mode and you also need to define those sprites in the Sprite Editor.

    Once the above has been done, you can then create the sprite asset.

    The following video is a bit old but most of its content is still applicable.
     
  3. sidespin

    sidespin

    Joined:
    Jun 22, 2018
    Posts:
    20
    Hi Stephan, thanks for replying. Actually I was referring to the new Sprite Packer which allows the use of .spriteatlas asset (references a folder and pack them behind the scene). TMP does not seem to work with this type of asset. Instead, I'll have to create a Multi-Sprite asset. This is a burden on the pipeline. Because every I add a new icon to the sprite it requires

    1. specify sprites source files to be packed
    2. create the spritesheet atlas texture/metadata using 3rd party software
    3. import it to Unity as Multi-Sprite and preserve the atlas information
    4. create/update the TMP Sprite Asset

    Is there any plan to support the new .spriteatlas format?
     
  4. Stephan_B

    Stephan_B

    Unity Technologies

    Joined:
    Feb 26, 2017
    Posts:
    6,582
    I would love to support the sprite atlas system. It is simply a matter of time / priorities. So much to do and so little time.

    P.S. In release 1.4.x and 2.0.x of TMP with Dynamic SDF support, the structure of Sprite Asset has changed to include a Sprite Character Table and Sprite Glyph Table. The Sprite Glyphs now include the Glyph Rect which corresponds to the UV coordinate of the sprite in the texture. This is the part that needs to get updated based on where the sprite atlas system placed the sprite glyph. In addition, we need to make sure we point to the correct texture as there could be more than one but this is something TMP already knows how to handle with font assets. All this to say, this could be implemented.
     
    AubreyH and Laicasaane like this.
  5. Laicasaane

    Laicasaane

    Joined:
    Apr 15, 2015
    Posts:
    150
    Suddenly our project also in need of this feature. We have a bunch of small sprites and just want to use Unity Sprite Atlas to pack them then create TMP Sprite Asset out of that atlas. We just don't want to use another tool to create and maintain a spritesheet.
     
  6. Stephan_B

    Stephan_B

    Unity Technologies

    Joined:
    Feb 26, 2017
    Posts:
    6,582
    I did explore adding support for the Unity Sprite Atlas but some functionality is not available yet. I am having discussions with the 2D Team but don't have an ETA from them as to when / if the required functionality will be added.

    The main issue is the Sprite Atlas pointing to the individual sprites in the Editor and to the atlas in playmode. The desired behavior is once packed, they should always point to their combined atlas. In addition, retrieving sprites results in allocations of clones of the sprites which is an issue as we want a reference to the actual source sprites.

    Hopefully, the 2D Team can add the required functionality for this to be supported.

    In the meantime, I understand the desire to not use an external tool but that is the only solution at this time.
     
    Laicasaane likes this.
  7. FeastSC2

    FeastSC2

    Joined:
    Sep 30, 2016
    Posts:
    968
    That would be really awesome because as it stands every time I have a new icon, I must recreate an entire sprite sheet with a 3rd party program and that takes time. Really looking forward to this feature!
     
  8. DimaHubenkoGamepoint

    DimaHubenkoGamepoint

    Joined:
    May 16, 2018
    Posts:
    20
    Bump, really needed feature. Also I'd like to say that we are interested in "animated sprites" in text - for example animated smiles. By now the only solution I see is to replace image in atlas by timer, but that requires SubMesh regeneration. This animated feature is needed only for same size sprites, so regeneration can be skipped...
    Is there any better proposition?
     
  9. Stephan_B

    Stephan_B

    Unity Technologies

    Joined:
    Feb 26, 2017
    Posts:
    6,582
    As per my previous post, adding support for the Sprite Atlas system would most certainly add value and something that I will be adding at some point. My challenge is simply too many things to do with so little time.

    With regards to animated sprites, this can be achieved using the <sprite> tag with anim attribute as seen in this example / post.
     
  10. Michal_Stangel

    Michal_Stangel

    Joined:
    Apr 17, 2017
    Posts:
    124
    Bump. Just migrated to Unity's Sprite Atlas system and realized that it cannot be used for TMP's sprite displaying.
     
  11. MaxGuernseyIII

    MaxGuernseyIII

    Joined:
    Aug 23, 2015
    Posts:
    315
    Yeah. I'm having a hard time figuring out how to use TMP in its current state. Everything I've seen shows me how to use a giant texture instead of what I have, which is one texture per image. Converting the many-loose textures into the consolidated mega-texture required by TMP seems time-consuming an error-prone. It seems like the cheapest course of action must be to write a build-time script that automatically generates one of these multi-sprite textures for the purpose of TMP every build. Is there a better option?
     
  12. Stephan_B

    Stephan_B

    Unity Technologies

    Joined:
    Feb 26, 2017
    Posts:
    6,582
    Until I get around / find the time to add support for the Sprite Atlas system, you can use an external tool like TexturePacker.

    TexturePacker makes it easy to take several separate images and to pack them into a single texture.

    To work with the TMP Sprite importer (Window - TextMeshPro - Sprite Importer), you have to use the JSON Array export option (included in the free version of TexturePacker). This produces a texture + data file (.json) that you will use to create the sprite asset.

    There should be several threads on the forum about using the Sprite Importer. Here is one such thread where some issue was reported which has now been resolved but the thread still contains useful information.
     
  13. MaxGuernseyIII

    MaxGuernseyIII

    Joined:
    Aug 23, 2015
    Posts:
    315
    Understandable. We all have to work to priority.

    I found a tool that helped along those lines (Simple Sprite Packer). It was not too hard to use. I forgot to update this thread.
     
    pixelR and FeastSC2 like this.
  14. pixelR

    pixelR

    Joined:
    Sep 16, 2013
    Posts:
    58
    Gosh, I also didn't expect that Sprite Atlas cannot be used for text sprites and that we have to maintain a sprite sheet separately, without any built-in tool available. :/ So yeah, I'd like to bump the feature request of supporting sprite atlas files.

    Edit: I'll give Simple Sprite Packer a go, thanks for the hint, @MaxGuernseyIII
     
  15. Kleptine

    Kleptine

    Joined:
    Dec 23, 2013
    Posts:
    203
    Yeah this is brutal.
     
  16. arcnor

    arcnor

    Joined:
    Nov 29, 2014
    Posts:
    17
    Suddenly found myself today in need of this, so adding my voice here (given that I cannot find a proper issue to vote / watch)
     
  17. bentama

    bentama

    Joined:
    Jan 5, 2014
    Posts:
    4
    Just hit this issue too. So I have to use a third party tool to pack sprites. Worse yet several TMP bugs exacerbate this workflow:
    1) If you change the sprite sheet you have to manually update the TMP sprite asset, but while the "Update Sprite Asset" command does that it doesn't save the changes to the .asset file so you will lose those changes. Workaround is to diddle a glyph setting so the dirty bit gets set and it is saved.
    2) The sprite packer tool creates an asset with the same name and TMP will overwrite that file without warning when you create the TMP sprite asset.
     
  18. ookk47oo

    ookk47oo

    Joined:
    Mar 17, 2017
    Posts:
    78
    Still not supported:(.......
    sc.png
     
  19. Knugke

    Knugke

    Joined:
    Jan 10, 2016
    Posts:
    3
    Another hopeful vote...
     
  20. trulden

    trulden

    Joined:
    Nov 12, 2016
    Posts:
    1
    Is there any progress on that feature?
     
  21. Stephan_B

    Stephan_B

    Unity Technologies

    Joined:
    Feb 26, 2017
    Posts:
    6,582
    I haven't had time to work on this feature for a while. However, I am still planning on adding support for it.

    In the meantime and if you need to be able to manage your sprite images individually, I would recommend using an external tool like TexturePacker.
     
    PigletPants likes this.
  22. PigletPants

    PigletPants

    Joined:
    Sep 19, 2019
    Posts:
    23
    In my case I have a character information sheet and it has quite a lot of icons on it that are in a sprite atlas so they can be batched. There is a subset of these icons that I would like to embed into tmp rich text. It seems like without adding support for tmp using unity sprite atlas directly I will be forced to duplicate these into a packed texture. So support for this feature would certainly be very nice. :)
     
    Stephan_B likes this.
  23. rzer

    rzer

    Joined:
    Aug 26, 2014
    Posts:
    2
    I wrote a little script you need to put in Editor folder. Next select atlas asset and select "Repack atlas to Sprite" from Conext menu. It will generate Sprite with Multiple mode. Maybe helpfull for someone. Pack algorithm is not perfect, but you can improve it by yourself. Atlas allowRotation and tightPacking must be set to false.


    Code (CSharp):
    1. using System;
    2. using System.Collections.Generic;
    3. using System.IO;
    4. using UnityEditor;
    5. using UnityEditor.U2D;
    6. using UnityEngine;
    7. using UnityEngine.U2D;
    8.  
    9. namespace Utils
    10. {
    11.     public class SpriteAtlasConverter
    12.     {
    13.    
    14.         [MenuItem("Assets/Repack atlas to sprite", true)]
    15.         private static bool RepackAtlasToSprite() {
    16.             return Selection.activeObject.GetType() == typeof(SpriteAtlas);
    17.         }
    18.    
    19.         [MenuItem("Assets/Repack atlas to sprite")]
    20.         static void RepackAtlasToSprite(MenuCommand command)
    21.         {
    22.        
    23.             SpriteAtlas atlas = (SpriteAtlas)Selection.activeObject;
    24.             var padding = atlas.GetPackingSettings().padding;
    25.        
    26.             //Pack sprites
    27.             Sprite[] sprites = new Sprite[atlas.spriteCount];
    28.             atlas.GetSprites(sprites);
    29.             var rects = new List<SpriteRect>();
    30.             foreach (var spr in sprites)
    31.             {
    32.                 rects.Add(new SpriteRect(spr, padding));
    33.             }
    34.             rects.Sort((a, b) => b.area.CompareTo(a.area));
    35.        
    36.             var packer = new RectanglePacker();
    37.        
    38.             foreach (var rect in rects)
    39.             {
    40.                 if (!packer.Pack(rect.w, rect.h, out rect.x, out rect.y))
    41.                     throw new Exception("Uh oh, we couldn't pack the rectangle :(");
    42.             }
    43.        
    44.             //Calculate image size
    45.             var maxSize = atlas.GetPlatformSettings("DefaultTexturePlatform").maxTextureSize;
    46.             var pngSize = Math.Max(packer.Width, packer.Height);
    47.             var powoftwo = 16;
    48.             while (powoftwo < pngSize) powoftwo *= 2;
    49.             pngSize = powoftwo;
    50.             if (pngSize > maxSize) pngSize = maxSize;
    51.             Texture2D texture = new Texture2D(pngSize, pngSize,TextureFormat.RGBA32,false);
    52.        
    53.             //Make texture transparent
    54.             Color fillColor = Color.clear;
    55.             Color[] fillPixels = new Color[texture.width * texture.height];
    56.             for (int i = 0; i < fillPixels.Length; i++) fillPixels[i] = fillColor;
    57.             texture.SetPixels(fillPixels);
    58.        
    59.             var metas = new List<SpriteMetaData>();
    60.        
    61.             //Draw sprites
    62.             foreach (var rect in rects)
    63.             {
    64.                 var t = GetReadableTexture(rect.sprite.texture);
    65.                 texture.SetPixels32(rect.x + padding, rect.y + padding, (int)rect.sprite.rect.width, (int)rect.sprite.rect.height, t.GetPixels32());
    66.                 metas.Add(new SpriteMetaData()
    67.                 {
    68.                     alignment = 6, //BottomLeft
    69.                     name = rect.sprite.name.Replace("(Clone)", ""),
    70.                     rect = new Rect(rect.x + padding, rect.y + padding, rect.sw, rect.sh)
    71.                 });
    72.            
    73.             }
    74.        
    75.             //Save image
    76.             var path = AssetDatabase.GetAssetPath(atlas);
    77.             var pngPath = path.Replace(".spriteatlas", ".png");
    78.        
    79.             Debug.Log($"Create sprite from atlas: {atlas.name} path: {path}");
    80.        
    81.             byte[] bytes = texture.EncodeToPNG();
    82.             File.WriteAllBytes(pngPath, bytes);
    83.        
    84.             //Update sprite settings
    85.             AssetDatabase.Refresh();
    86.        
    87.             TextureImporter ti = AssetImporter.GetAtPath(pngPath) as TextureImporter;
    88.             ti.textureType = TextureImporterType.Sprite;
    89.             ti.spriteImportMode = SpriteImportMode.Multiple;
    90.             ti.spritesheet = metas.ToArray();
    91.        
    92.             EditorUtility.SetDirty(ti);
    93.             ti.SaveAndReimport();
    94.  
    95.         }
    96.    
    97.         private static Texture2D GetReadableTexture(Texture2D source) {
    98.  
    99.             RenderTexture tmp = RenderTexture.GetTemporary(
    100.                 source.width,
    101.                 source.height,
    102.                 0,
    103.                 RenderTextureFormat.ARGB32,
    104.                 RenderTextureReadWrite.Linear);
    105.  
    106.             Graphics.Blit(source, tmp);
    107.             RenderTexture previous = RenderTexture.active;
    108.             RenderTexture.active = tmp;
    109.             Texture2D result = new Texture2D(source.width, source.height);
    110.             result.ReadPixels(new Rect(0, 0, tmp.width, tmp.height), 0, 0);
    111.             result.Apply();
    112.             RenderTexture.active = previous;
    113.             RenderTexture.ReleaseTemporary(tmp);
    114.             return result;
    115.         }
    116.     }
    117.  
    118.     public class SpriteRect
    119.     {
    120.         public Sprite sprite;
    121.         public int x, y, w, h;
    122.  
    123.         public SpriteRect(Sprite sprite, int padding)
    124.         {
    125.             this.sprite = sprite;
    126.             this.x = 0;
    127.             this.y = 0;
    128.             this.w = (int) sw + padding*2;
    129.             this.h = (int) sh + padding*2;
    130.         }
    131.  
    132.         public int sw => (int) sprite.rect.width;
    133.         public int sh => (int) sprite.rect.height;
    134.         public int area => w * h;
    135.     }
    136.  
    137.     // https://github.com/mikaturunen/RectanglePacker
    138.     public class RectanglePacker
    139.     {
    140.         public int Width { get; private set; }
    141.         public int Height { get; private set; }
    142.  
    143.         List<Node> nodes = new List<Node>();
    144.  
    145.         public RectanglePacker()
    146.         {
    147.             nodes.Add(new Node(0, 0, int.MaxValue, int.MaxValue));
    148.         }
    149.  
    150.         public bool Pack(int w, int h, out int x, out int y)
    151.         {
    152.             for (int i = 0; i < nodes.Count; ++i)
    153.             {
    154.                 if (w <= nodes[i].W && h <= nodes[i].H)
    155.                 {
    156.                     var node = nodes[i];
    157.                     nodes.RemoveAt(i);
    158.                     x = node.X;
    159.                     y = node.Y;
    160.                     int r = x + w;
    161.                     int b = y + h;
    162.                     nodes.Add(new Node(r, y, node.Right - r, h));
    163.                     nodes.Add(new Node(x, b, w, node.Bottom - b));
    164.                     nodes.Add(new Node(r, b, node.Right - r, node.Bottom - b));
    165.                     Width = Math.Max(Width, r);
    166.                     Height = Math.Max(Height, b);
    167.                     return true;
    168.                 }
    169.             }
    170.             x = 0;
    171.             y = 0;
    172.             return false;
    173.         }
    174.  
    175.         public struct Node
    176.         {
    177.             public int X;
    178.             public int Y;
    179.             public int W;
    180.             public int H;
    181.  
    182.             public Node(int x, int y, int w, int h)
    183.             {
    184.                 X = x;
    185.                 Y = y;
    186.                 W = w;
    187.                 H = h;
    188.             }
    189.  
    190.             public int Right
    191.             {
    192.                 get { return X + W; }
    193.             }
    194.  
    195.             public int Bottom
    196.             {
    197.                 get { return Y + H; }
    198.             }
    199.         }
    200.     }
    201.  
    202. }
     
    Last edited: Oct 19, 2021
  24. Garfounkel

    Garfounkel

    Joined:
    Mar 12, 2015
    Posts:
    3
    Just wanted to point out for others that this worked like a charm. It even correctly named the spritesheet sub-sprites according to the original PNG filenames. Very useful, thanks @rzer
     
  25. DiegoDePalacio

    DiegoDePalacio

    Unity Technologies

    Joined:
    Oct 28, 2009
    Posts:
    506
    Thank you @rzer,

    In my case, the atlas was generated with the wrong colors on the sprites.

    I solved this by replacing the line:

    RenderTextureReadWrite.Linear);

    with

    RenderTextureReadWrite.sRGB);


    Posting it here, for someone else having the same issue in the future as myself.
     
  26. Z4urce

    Z4urce

    Joined:
    Mar 22, 2013
    Posts:
    6
    Any update on this?
     
    Last edited: Apr 14, 2022
  27. skilani

    skilani

    Joined:
    Aug 31, 2013
    Posts:
    7
    This is incredible, works like a god damn charm. Thank you
     
  28. won-gyu

    won-gyu

    Joined:
    Mar 23, 2018
    Posts:
    9
    Thank you @rzer,

    With SpriteAtlas V2, I had NullReference Exception.

    I solved it by replacing the line:
    Code (CSharp):
    1. var pngPath = path.Replace(".spriteatlas", ".png");
    with
    Code (CSharp):
    1. var pngPath = path.Replace(".spriteatlasv2", ".png");
     
    M4Mahdi likes this.
  29. Fressbrett

    Fressbrett

    Joined:
    Apr 11, 2018
    Posts:
    76
    The feature request is now over 3 years old. Any updates on this? This feels like an essential feature, allowing one to display many different icons inline within TextMeshPro, without having to have all of them on the same Spritesheet.
     
    thefallengamesstudio likes this.
  30. thefallengamesstudio

    thefallengamesstudio

    Joined:
    Mar 7, 2016
    Posts:
    672
  31. EyeDev44

    EyeDev44

    Joined:
    Apr 8, 2017
    Posts:
    119
  32. M4Mahdi

    M4Mahdi

    Joined:
    Dec 17, 2018
    Posts:
    1
    Thank you @rzer, @DiegoDePalacio and @won-gyu !
    Same script with some little improvements:
    1. Selected asset will check to be not null to avoid error in Console
    2. enableRotation & enableTightPacking are checking to be false as @rzer described
    3. RenderTextureReadWrite.Linear replaced with RenderTextureReadWrite.sRGB as @DiegoDePalacio described
    4. Supporting sprite atlas v1 and v2 as @won-gyu described
    5. New same output will not replacing older if creating from same atlas. It will rename each time like method in duplicating
    * Don't forget that "Max Texture Size" in "sprite atlas asset" will determine width & height of output

    Code (CSharp):
    1.  
    2. /*
    3. https://forum.unity.com/threads/text-mesh-pro-does-not-work-with-spriteatlas-assets.697088/#post-7581571
    4.  
    5. I wrote a little script you need to put in Editor folder.
    6. Next select atlas asset and select "Repack atlas to Sprite" from Conext menu.
    7. It will generate Sprite with Multiple mode. Maybe helpfull for someone.
    8. Pack algorithm is not perfect, but you can improve it by yourself.
    9. Atlas allowRotation and tightPacking must be set to false.
    10. */
    11.  
    12. using System;
    13. using System.Collections.Generic;
    14. using System.IO;
    15. using UnityEditor;
    16. using UnityEditor.U2D;
    17. using UnityEngine;
    18. using UnityEngine.U2D;
    19.  
    20. namespace Utils
    21. {
    22.     public class SpriteAtlasConverter
    23.     {
    24.  
    25.         [MenuItem("Assets/Repack atlas to sprite", true)]
    26.         private static bool RepackAtlasToSprite()
    27.         {
    28.             return Selection.activeObject != null && Selection.activeObject.GetType() == typeof(SpriteAtlas);
    29.         }
    30.  
    31.         [MenuItem("Assets/Repack atlas to sprite")]
    32.         static void RepackAtlasToSprite(MenuCommand command)
    33.         {
    34.  
    35.             SpriteAtlas atlas = (SpriteAtlas)Selection.activeObject;
    36.  
    37.             if (atlas.GetPackingSettings().enableRotation || atlas.GetPackingSettings().enableTightPacking)
    38.             {
    39.                 Debug.LogError($"Problem! Atlas 'allowRotation' and 'tightPacking' must be set to false in '{Selection.activeObject.name}' to work correctly!", Selection.activeObject);
    40.                 return;
    41.             }
    42.  
    43.             var padding = atlas.GetPackingSettings().padding;
    44.  
    45.             //Pack sprites
    46.             Sprite[] sprites = new Sprite[atlas.spriteCount];
    47.             atlas.GetSprites(sprites);
    48.             var rects = new List<SpriteRect>();
    49.             foreach (var spr in sprites)
    50.             {
    51.                 rects.Add(new SpriteRect(spr, padding));
    52.             }
    53.             rects.Sort((a, b) => b.area.CompareTo(a.area));
    54.  
    55.             var packer = new RectanglePacker();
    56.  
    57.             foreach (var rect in rects)
    58.             {
    59.                 if (!packer.Pack(rect.w, rect.h, out rect.x, out rect.y))
    60.                     throw new Exception("Uh oh, we couldn't pack the rectangle :(");
    61.             }
    62.  
    63.             //Calculate image size
    64.             var maxSize = atlas.GetPlatformSettings("DefaultTexturePlatform").maxTextureSize;
    65.             var pngSize = Math.Max(packer.Width, packer.Height);
    66.             var powoftwo = 16;
    67.             while (powoftwo < pngSize) powoftwo *= 2;
    68.             pngSize = powoftwo;
    69.             if (pngSize > maxSize) pngSize = maxSize;
    70.             Texture2D texture = new Texture2D(pngSize, pngSize, TextureFormat.RGBA32, false);
    71.  
    72.             //Make texture transparent
    73.             Color fillColor = Color.clear;
    74.             Color[] fillPixels = new Color[texture.width * texture.height];
    75.             for (int i = 0; i < fillPixels.Length; i++) fillPixels[i] = fillColor;
    76.             texture.SetPixels(fillPixels);
    77.  
    78.             var metas = new List<SpriteMetaData>();
    79.  
    80.             //Draw sprites
    81.             foreach (var rect in rects)
    82.             {
    83.                 var t = GetReadableTexture(rect.sprite.texture);
    84.                 texture.SetPixels32(rect.x + padding, rect.y + padding, (int)rect.sprite.rect.width, (int)rect.sprite.rect.height, t.GetPixels32());
    85.                 metas.Add(new SpriteMetaData()
    86.                 {
    87.                     alignment = 6, //BottomLeft
    88.                     name = rect.sprite.name.Replace("(Clone)", ""),
    89.                     rect = new Rect(rect.x + padding, rect.y + padding, rect.sw, rect.sh)
    90.                 });
    91.  
    92.             }
    93.  
    94.             //Save image
    95.             var path = AssetDatabase.GetAssetPath(atlas);
    96.             //var pngPath = path.Replace(".spriteatlas", ".png");
    97.             var pngPath = path.Replace(".spriteatlasv2", ".png").Replace(".spriteatlas", ".png");//To support v2
    98.  
    99.             Debug.Log($"Create sprite from atlas: {atlas.name} path: {path}");
    100.  
    101.             byte[] bytes = texture.EncodeToPNG();
    102.             var tempPngPath = pngPath;//To prevent bug in number of naming
    103.             int j = 1;
    104.             while (File.Exists(tempPngPath))
    105.             {
    106.                 tempPngPath = Path.Combine(Path.GetDirectoryName(pngPath), $"{Path.GetFileNameWithoutExtension(pngPath)} {j}{Path.GetExtension(pngPath)}");
    107.                 j++;
    108.             }
    109.             pngPath = tempPngPath;
    110.             File.WriteAllBytes(pngPath, bytes);
    111.  
    112.             //Update sprite settings
    113.             AssetDatabase.Refresh();
    114.  
    115.             TextureImporter ti = AssetImporter.GetAtPath(pngPath) as TextureImporter;
    116.             ti.textureType = TextureImporterType.Sprite;
    117.             ti.spriteImportMode = SpriteImportMode.Multiple;
    118.             ti.spritesheet = metas.ToArray();
    119.  
    120.             EditorUtility.SetDirty(ti);
    121.             ti.SaveAndReimport();
    122.  
    123.         }
    124.  
    125.         private static Texture2D GetReadableTexture(Texture2D source)
    126.         {
    127.  
    128.             RenderTexture tmp = RenderTexture.GetTemporary(
    129.                 source.width,
    130.                 source.height,
    131.                 0,
    132.                 RenderTextureFormat.ARGB32,
    133.                 RenderTextureReadWrite.sRGB);//RenderTextureReadWrite.Linear//sRGB replaced to prevent bug(?)
    134.  
    135.             Graphics.Blit(source, tmp);
    136.             RenderTexture previous = RenderTexture.active;
    137.             RenderTexture.active = tmp;
    138.             Texture2D result = new Texture2D(source.width, source.height);
    139.             result.ReadPixels(new Rect(0, 0, tmp.width, tmp.height), 0, 0);
    140.             result.Apply();
    141.             RenderTexture.active = previous;
    142.             RenderTexture.ReleaseTemporary(tmp);
    143.             return result;
    144.         }
    145.     }
    146.  
    147.     public class SpriteRect
    148.     {
    149.         public Sprite sprite;
    150.         public int x, y, w, h;
    151.  
    152.         public SpriteRect(Sprite sprite, int padding)
    153.         {
    154.             this.sprite = sprite;
    155.             this.x = 0;
    156.             this.y = 0;
    157.             this.w = (int)sw + padding * 2;
    158.             this.h = (int)sh + padding * 2;
    159.         }
    160.  
    161.         public int sw => (int)sprite.rect.width;
    162.         public int sh => (int)sprite.rect.height;
    163.         public int area => w * h;
    164.     }
    165.  
    166.     // https://github.com/mikaturunen/RectanglePacker
    167.     public class RectanglePacker
    168.     {
    169.         public int Width { get; private set; }
    170.         public int Height { get; private set; }
    171.  
    172.         List<Node> nodes = new List<Node>();
    173.  
    174.         public RectanglePacker()
    175.         {
    176.             nodes.Add(new Node(0, 0, int.MaxValue, int.MaxValue));
    177.         }
    178.  
    179.         public bool Pack(int w, int h, out int x, out int y)
    180.         {
    181.             for (int i = 0; i < nodes.Count; ++i)
    182.             {
    183.                 if (w <= nodes[i].W && h <= nodes[i].H)
    184.                 {
    185.                     var node = nodes[i];
    186.                     nodes.RemoveAt(i);
    187.                     x = node.X;
    188.                     y = node.Y;
    189.                     int r = x + w;
    190.                     int b = y + h;
    191.                     nodes.Add(new Node(r, y, node.Right - r, h));
    192.                     nodes.Add(new Node(x, b, w, node.Bottom - b));
    193.                     nodes.Add(new Node(r, b, node.Right - r, node.Bottom - b));
    194.                     Width = Math.Max(Width, r);
    195.                     Height = Math.Max(Height, b);
    196.                     return true;
    197.                 }
    198.             }
    199.             x = 0;
    200.             y = 0;
    201.             return false;
    202.         }
    203.  
    204.         public struct Node
    205.         {
    206.             public int X;
    207.             public int Y;
    208.             public int W;
    209.             public int H;
    210.  
    211.             public Node(int x, int y, int w, int h)
    212.             {
    213.                 X = x;
    214.                 Y = y;
    215.                 W = w;
    216.                 H = h;
    217.             }
    218.  
    219.             public int Right
    220.             {
    221.                 get { return X + W; }
    222.             }
    223.  
    224.             public int Bottom
    225.             {
    226.                 get { return Y + H; }
    227.             }
    228.         }
    229.     }
    230.  
    231. }
    232.  
    233.  
     
    Last edited: Nov 12, 2022
    won-gyu likes this.
  33. thefallengamesstudio

    thefallengamesstudio

    Joined:
    Mar 7, 2016
    Posts:
    672
    Thanks!

    I added few more things to it.

    You can select linear or rgb via dialog (sometimes it works as linear, sometimes not).
    Slightly better handling of spriteatlasv2.
    Fixing case when the target file already exists (appends 1 or 2 or 3 etc. until the file doesn't exist).


    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using System.IO;
    5. using UnityEditor;
    6. using UnityEditor.U2D;
    7. using UnityEngine.U2D;
    8. using System;
    9.  
    10. namespace Plugins.SimpleTexturePacker.Editor
    11. {
    12.  
    13.     /// <summary>
    14.     /// Taken from: https://forum.unity.com/threads/text-mesh-pro-does-not-work-with-spriteatlas-assets.697088/#post-7581571
    15.     /// All credits go to the original author
    16.     /// Original name: SimpleSpriteAtlasConverter
    17.     /// Atlas allowRotation and tightPacking must be set to false.
    18.     /// Select a Sprite atlas and go to "Assets/SimpleSpriteAtlasConverter - Repack atlas to sprite" to convert it to a sprite.
    19.     /// This is most useful to add a texture to TMPro without requiring managing a tpsheet project for TexturePacker
    20.     /// </summary>
    21.     public class SimpleTexturePacker
    22.     {
    23.  
    24.         [MenuItem("Assets/SimpleSpriteAtlasConverter - Repack atlas to sprite", true)]
    25.         private static bool RepackAtlasToSprite()
    26.         {
    27.             if (Selection.activeObject == null)
    28.             {
    29.                 Debug.Log("No object selected");
    30.                 return false;
    31.             }
    32.  
    33.             return Selection.activeObject.GetType() == typeof(SpriteAtlas);
    34.         }
    35.  
    36.         [MenuItem("Assets/SimpleSpriteAtlasConverter - Repack atlas to sprite")]
    37.         static void RepackAtlasToSprite(MenuCommand command)
    38.         {
    39.  
    40.             SpriteAtlas atlas = (SpriteAtlas)Selection.activeObject;
    41.  
    42.             if (atlas.GetPackingSettings().enableRotation || atlas.GetPackingSettings().enableTightPacking)
    43.             {
    44.                 Debug.LogError($"Problem! Atlas 'allowRotation' and 'tightPacking' must be set to false in '{Selection.activeObject.name}' to work correctly!", Selection.activeObject);
    45.                 return;
    46.             }
    47.  
    48.             var padding = atlas.GetPackingSettings().padding;
    49.  
    50.             //Pack sprites
    51.             Sprite[] sprites = new Sprite[atlas.spriteCount];
    52.             atlas.GetSprites(sprites);
    53.             var rects = new List<SpriteRect>();
    54.             foreach (var spr in sprites)
    55.             {
    56.                 rects.Add(new SpriteRect(spr, padding));
    57.             }
    58.             rects.Sort((a, b) => b.area.CompareTo(a.area));
    59.  
    60.             var packer = new RectanglePacker();
    61.  
    62.             foreach (var rect in rects)
    63.             {
    64.                 if (!packer.Pack(rect.w, rect.h, out rect.x, out rect.y))
    65.                     throw new Exception("Uh oh, we couldn't pack the rectangle :(");
    66.             }
    67.  
    68.             //Calculate image size
    69.             var maxSize = atlas.GetPlatformSettings("DefaultTexturePlatform").maxTextureSize;
    70.             var pngSize = Math.Max(packer.Width, packer.Height);
    71.             var powoftwo = 16;
    72.             while (powoftwo < pngSize) powoftwo *= 2;
    73.             pngSize = powoftwo;
    74.             if (pngSize > maxSize) pngSize = maxSize;
    75.  
    76.             // LL added: seems like some folks need sRGB, so we add a choice for that
    77.             var optionLinear = RenderTextureReadWrite.Linear;
    78.             var optionSrgb = RenderTextureReadWrite.sRGB;
    79.             var option = EditorUtility.DisplayDialogComplex(
    80.                 "Choose texture format",
    81.                 "This will be the target texture format. If in doubt, use Linear.",
    82.                 optionLinear.ToString(),
    83.                 optionSrgb.ToString(),
    84.                 "Cancel"
    85.             );
    86.             RenderTextureReadWrite renderTextureReadWrite;
    87.             if (option == 0)
    88.                 renderTextureReadWrite = optionLinear;
    89.             else if (option == 1)
    90.                 renderTextureReadWrite = optionSrgb;
    91.             else
    92.             {
    93.                 Debug.Log("Cancelled packing texture");
    94.                 return;
    95.             }
    96.  
    97.             Texture2D texture = new Texture2D(pngSize, pngSize, TextureFormat.RGBA32, false);
    98.  
    99.             //Make texture transparent
    100.             Color fillColor = Color.clear;
    101.             Color[] fillPixels = new Color[texture.width * texture.height];
    102.             for (int i = 0; i < fillPixels.Length; i++) fillPixels[i] = fillColor;
    103.             texture.SetPixels(fillPixels);
    104.  
    105.             var metas = new List<SpriteMetaData>();
    106.  
    107.             //Draw sprites
    108.             foreach (var rect in rects)
    109.             {
    110.                 var t = GetReadableTexture(rect.sprite.texture, renderTextureReadWrite);
    111.                 texture.SetPixels32(rect.x + padding, rect.y + padding, (int)rect.sprite.rect.width, (int)rect.sprite.rect.height, t.GetPixels32());
    112.                 metas.Add(new SpriteMetaData()
    113.                 {
    114.                     alignment = 6, //BottomLeft
    115.                     name = rect.sprite.name.Replace("(Clone)", ""),
    116.                     rect = new Rect(rect.x + padding, rect.y + padding, rect.sw, rect.sh)
    117.                 });
    118.  
    119.             }
    120.  
    121.             //Save image
    122.             var path = AssetDatabase.GetAssetPath(atlas);
    123.  
    124.             // LL: handling Sprite Atlas V2 case
    125.             string pngPath;
    126.             if (path.EndsWith(".spriteatlas"))
    127.                 pngPath = path.Replace(".spriteatlas", ".png");
    128.             else if (path.EndsWith(".spriteatlasv2"))
    129.                 pngPath = path.Replace(".spriteatlasv2", ".png");
    130.             else
    131.                 throw new InvalidOperationException($"Unexpected file extension: {path}");
    132.  
    133.             Debug.Log($"Create sprite from atlas: {atlas.name} path: {path}");
    134.  
    135.             byte[] bytes = texture.EncodeToPNG();
    136.             var tempPngPath = pngPath;//To prevent bug in number of naming
    137.             int j = 1;
    138.             while (File.Exists(tempPngPath))
    139.             {
    140.                 tempPngPath = Path.Combine(Path.GetDirectoryName(pngPath), $"{Path.GetFileNameWithoutExtension(pngPath)} {j}{Path.GetExtension(pngPath)}");
    141.                 j++;
    142.             }
    143.             pngPath = tempPngPath;
    144.             File.WriteAllBytes(pngPath, bytes);
    145.  
    146.             //Update sprite settings
    147.             AssetDatabase.Refresh();
    148.  
    149.             TextureImporter ti = AssetImporter.GetAtPath(pngPath) as TextureImporter;
    150.             ti.textureType = TextureImporterType.Sprite;
    151.             ti.spriteImportMode = SpriteImportMode.Multiple;
    152.             ti.spritesheet = metas.ToArray();
    153.  
    154.             EditorUtility.SetDirty(ti);
    155.             ti.SaveAndReimport();
    156.  
    157.         }
    158.  
    159.         private static Texture2D GetReadableTexture(Texture2D source, RenderTextureReadWrite readWriteMode)
    160.         {
    161.  
    162.             RenderTexture tmp = RenderTexture.GetTemporary(
    163.                 source.width,
    164.                 source.height,
    165.                 0,
    166.                 RenderTextureFormat.ARGB32,
    167.                 readWriteMode);
    168.  
    169.             Graphics.Blit(source, tmp);
    170.             RenderTexture previous = RenderTexture.active;
    171.             RenderTexture.active = tmp;
    172.             Texture2D result = new Texture2D(source.width, source.height);
    173.             result.ReadPixels(new Rect(0, 0, tmp.width, tmp.height), 0, 0);
    174.             result.Apply();
    175.             RenderTexture.active = previous;
    176.             RenderTexture.ReleaseTemporary(tmp);
    177.             return result;
    178.         }
    179.     }
    180.  
    181.     public class SpriteRect
    182.     {
    183.         public Sprite sprite;
    184.         public int x, y, w, h;
    185.  
    186.         public SpriteRect(Sprite sprite, int padding)
    187.         {
    188.             this.sprite = sprite;
    189.             this.x = 0;
    190.             this.y = 0;
    191.             this.w = (int)sw + padding * 2;
    192.             this.h = (int)sh + padding * 2;
    193.         }
    194.  
    195.         public int sw => (int)sprite.rect.width;
    196.         public int sh => (int)sprite.rect.height;
    197.         public int area => w * h;
    198.     }
    199.  
    200.     // https://github.com/mikaturunen/RectanglePacker
    201.     public class RectanglePacker
    202.     {
    203.         public int Width { get; private set; }
    204.         public int Height { get; private set; }
    205.  
    206.         List<Node> nodes = new List<Node>();
    207.  
    208.         public RectanglePacker()
    209.         {
    210.             nodes.Add(new Node(0, 0, int.MaxValue, int.MaxValue));
    211.         }
    212.  
    213.         public bool Pack(int w, int h, out int x, out int y)
    214.         {
    215.             for (int i = 0; i < nodes.Count; ++i)
    216.             {
    217.                 if (w <= nodes[i].W && h <= nodes[i].H)
    218.                 {
    219.                     var node = nodes[i];
    220.                     nodes.RemoveAt(i);
    221.                     x = node.X;
    222.                     y = node.Y;
    223.                     int r = x + w;
    224.                     int b = y + h;
    225.                     nodes.Add(new Node(r, y, node.Right - r, h));
    226.                     nodes.Add(new Node(x, b, w, node.Bottom - b));
    227.                     nodes.Add(new Node(r, b, node.Right - r, node.Bottom - b));
    228.                     Width = Math.Max(Width, r);
    229.                     Height = Math.Max(Height, b);
    230.                     return true;
    231.                 }
    232.             }
    233.             x = 0;
    234.             y = 0;
    235.             return false;
    236.         }
    237.  
    238.         public struct Node
    239.         {
    240.             public int X;
    241.             public int Y;
    242.             public int W;
    243.             public int H;
    244.  
    245.             public Node(int x, int y, int w, int h)
    246.             {
    247.                 X = x;
    248.                 Y = y;
    249.                 W = w;
    250.                 H = h;
    251.             }
    252.  
    253.             public int Right
    254.             {
    255.                 get { return X + W; }
    256.             }
    257.  
    258.             public int Bottom
    259.             {
    260.                 get { return Y + H; }
    261.             }
    262.         }
    263.     }
    264. }
    265.  
     
    rzer and won-gyu like this.