Search Unity

  1. Are you interested in providing feedback directly to Unity teams? Sign up to become a member of Unity Pulse, our new product feedback and research community.
    Dismiss Notice

Sprite Editor Automatic Slicing by Script

Discussion in 'Editor & General Support' started by BehemothPro, Apr 22, 2015.

  1. BehemothPro

    BehemothPro

    Joined:
    Feb 14, 2015
    Posts:
    19
    I'm trying to find a way to automatically slice a sprite texture into multiple sprites through an editor script instead of manually having to click the slice button for each of my textures. Is there a function I can call for that?
     
    floriancourgey likes this.
  2. TomasJ

    TomasJ

    Joined:
    Sep 26, 2010
    Posts:
    256
  3. BehemothPro

    BehemothPro

    Joined:
    Feb 14, 2015
    Posts:
    19
    Thanks, but I was hoping for a way to set the SpriteMetaData through Unity's method of slicing or is that not accessible?
     
  4. aer0ace

    aer0ace

    Joined:
    May 11, 2012
    Posts:
    1,376
    I wanted to add to this, since I sort of had the same problem, yet there were several broken solutions that I found online.
    Below is one way of doing it, with the assumption that the spritesheet consists of evenly-cut frames, say exported from aseprite or something. My frames are each 64x64.

    The process goes like this:
    1. OnPreprocessTexture() -> Set up the TextureImporter. At this point, you can't set up the SpriteMetadata, which specifies the individual Sprites in the Spritesheet, because the Texture hasn't been processed yet.
    2. OnPostProcessTexture() -> Now that the texture has been processed, I have the dimensions of the texture, which I can use to slice up evenly, and determine the rows and columns in the sprite sheet, and store in the SpriteMetadata in the TextureImporter
    3. OnPostprocessSprites() -> This should result in the sliced sprites as the second param.

    Code (CSharp):
    1. public class SpriteProcessor : AssetPostprocessor
    2. {
    3.     void OnPreprocessTexture ()
    4.     {
    5.         TextureImporter textureImporter = (TextureImporter)assetImporter;
    6.         textureImporter.textureType = TextureImporterType.Sprite;
    7.         textureImporter.spriteImportMode = SpriteImportMode.Multiple;
    8.         textureImporter.mipmapEnabled = false;
    9.         textureImporter.filterMode = FilterMode.Point;
    10.  
    11.     }
    12.  
    13.     public void OnPostprocessTexture (Texture2D texture)
    14.     {
    15.         Debug.Log("Texture2D: (" + texture.width + "x" + texture.height + ")");
    16.  
    17.      
    18.  
    19.         int spriteSize = 64;
    20.         int colCount = texture.width / spriteSize;
    21.         int rowCount = texture.height / spriteSize;
    22.  
    23.         List<SpriteMetaData> metas = new List<SpriteMetaData>();
    24.  
    25.         for (int r = 0; r < rowCount; ++r)
    26.         {
    27.             for (int c = 0; c < colCount; ++c)
    28.             {
    29.                 SpriteMetaData meta = new SpriteMetaData();
    30.                 meta.rect = new Rect(c * spriteSize, r * spriteSize, spriteSize, spriteSize);
    31.                 meta.name = c + "-" + r;
    32.                 metas.Add(meta);
    33.             }
    34.         }
    35.  
    36.         TextureImporter textureImporter = (TextureImporter)assetImporter;
    37.         textureImporter.spritesheet = metas.ToArray();
    38.     }
    39.  
    40.     public void OnPostprocessSprites(Texture2D texture, Sprite[] sprites)
    41.     {
    42.         Debug.Log("Sprites: " + sprites.Length);
    43.     }
    44. }
     
    Wichenstaden likes this.
  5. sarahnorthway

    sarahnorthway

    Joined:
    Jul 16, 2015
    Posts:
    43
    You can now (Unity 2017.3) access InternalSpriteUtility.GenerateAutomaticSpriteRectangles to automatically slice up a texture into sprites during OnPostprocessTexture. Code:

    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEditor;
    3. using System.Collections.Generic;
    4. using UnityEditorInternal;
    5. using System.IO;
    6.  
    7. // https://forum.unity.com/threads/sprite-editor-automatic-slicing-by-script.320776/
    8. // http://www.sarpersoher.com/a-custom-asset-importer-for-unity/
    9. public class SpritePostprocessor : AssetPostprocessor {
    10.  
    11.     /// <summary>
    12.     /// Default all textures to 2D sprites, pivot at bottom, mip-mapped, uncompressed.
    13.     /// </summary>
    14.     private void OnPreprocessTexture() {
    15.         Debug.Log("OnPreprocessTexture overwriting defaults");
    16.  
    17.         TextureImporter importer = assetImporter as TextureImporter;
    18.         importer.textureType = TextureImporterType.Sprite;
    19.  
    20.         importer.spriteImportMode = SpriteImportMode.Multiple;
    21.  
    22.         importer.mipmapEnabled = true;
    23.         importer.filterMode = FilterMode.Trilinear;
    24.         importer.textureCompression = TextureImporterCompression.Uncompressed;
    25.     }
    26.  
    27.     public void OnPostprocessTexture(Texture2D texture) {
    28.         TextureImporter importer = assetImporter as TextureImporter;
    29.         if (importer.spriteImportMode != SpriteImportMode.Multiple) {
    30.             return;
    31.         }
    32.  
    33.         Debug.Log("OnPostprocessTexture generating sprites");
    34.  
    35.         int minimumSpriteSize = 16;
    36.         int extrudeSize = 0;
    37.         Rect[] rects = InternalSpriteUtility.GenerateAutomaticSpriteRectangles(texture, minimumSpriteSize, extrudeSize);
    38.         List<Rect> rectsList = new List<Rect>(rects);
    39.         rectsList = SortRects(rectsList, texture.width);
    40.  
    41.         string filenameNoExtension = Path.GetFileNameWithoutExtension(assetPath);
    42.         List<SpriteMetaData> metas = new List<SpriteMetaData>();
    43.         int rectNum = 0;
    44.  
    45.         foreach (Rect rect in rectsList) {
    46.             SpriteMetaData meta = new SpriteMetaData();
    47.             meta.rect = rect;
    48.             meta.name = filenameNoExtension + "_" + rectNum++;
    49.             metas.Add(meta);
    50.         }
    51.  
    52.         importer.spritesheet = metas.ToArray();
    53.     }
    54.  
    55.     public void OnPostprocessSprites(Texture2D texture, Sprite[] sprites) {
    56.         Debug.Log("OnPostprocessSprites sprites: " + sprites.Length);
    57.     }
    58.  
    59.     private List<Rect> SortRects(List<Rect> rects, float textureWidth) {
    60.         List<Rect> list = new List<Rect>();
    61.         while (rects.Count > 0) {
    62.             Rect rect = rects[rects.Count - 1];
    63.             Rect sweepRect = new Rect(0f, rect.yMin, textureWidth, rect.height);
    64.             List<Rect> list2 = this.RectSweep(rects, sweepRect);
    65.             if (list2.Count <= 0) {
    66.                 list.AddRange(rects);
    67.                 break;
    68.             }
    69.             list.AddRange(list2);
    70.         }
    71.         return list;
    72.     }
    73.  
    74.     private List<Rect> RectSweep(List<Rect> rects, Rect sweepRect) {
    75.         List<Rect> result;
    76.         if (rects == null || rects.Count == 0) {
    77.             result = new List<Rect>();
    78.         } else {
    79.             List<Rect> list = new List<Rect>();
    80.             foreach (Rect current in rects) {
    81.                 if (current.Overlaps(sweepRect)) {
    82.                     list.Add(current);
    83.                 }
    84.             }
    85.             foreach (Rect current2 in list) {
    86.                 rects.Remove(current2);
    87.             }
    88.             list.Sort((Rect a, Rect b) => a.x.CompareTo(b.x));
    89.             result = list;
    90.         }
    91.         return result;
    92.     }
    93. }
    94.  
     
  6. ChiuanWei

    ChiuanWei

    Joined:
    Jan 29, 2012
    Posts:
    111
    not good. and how to call the sprite editor slice function
     
  7. MrCabbages

    MrCabbages

    Joined:
    Oct 25, 2016
    Posts:
    2
    @sarahnorthway This doesn't seem to do anything for me. It's running properly on import, but always generating 0 sprites.

    Quick Edit: I've fixed it but I have no idea why. Right before you call GenerateAutomaticSpriteRectangles, I added a "Debug.Log(texture)" to make sure it wasn't null... and now it works. If I take out the Debug.Log, it doesn't work. I'm on 2018b13

    Quick Edit 2: I took it out to test it and added it back in and now it doesn't work again, even with the debug.log in place. Should've just left it alone.
     
    Last edited: Apr 24, 2018
  8. Speedrun-Labs

    Speedrun-Labs

    Joined:
    Aug 1, 2014
    Posts:
    14
    After several hours of dickin' around with the script I found the answer!

    Add: AssetDatabase.Refresh(); After this line: importer.spritesheet = metas.ToArray();


    Note: when you import textures and the script runs there will be a console message saying:
    "A default asset was created for 'Assets/blah.png' because the asset importer crashed on it last time."

    Anyone know what this means or how to fix it? (The script still works fine, just don't like errors ;) )
     
    Last edited: May 11, 2018
    FranekKimono likes this.
  9. Speedrun-Labs

    Speedrun-Labs

    Joined:
    Aug 1, 2014
    Posts:
    14
  10. Alex_May

    Alex_May

    Joined:
    Dec 29, 2013
    Posts:
    196
    Note for travellers: InternalSpriteUtility.GenerateAutomaticSpriteRectangles currently does not work properly for NPOT textures. Of course, your textures shouldn't be NPOT anyway, but there it is.
     
    chenshajie likes this.
  11. me_gone_mad1

    me_gone_mad1

    Joined:
    Feb 2, 2018
    Posts:
    9
    # late to the party but
    i remixed sarahnorthway's and aer0ace's code,
    the premise it auto slices to get a count of sprites in the sheet, then picks the largest sprites center and finds all sprites that are in the same row/column to get the row/column count then uses those to get a grid slice.

    Code (CSharp):
    1.     public void OnPostprocessTexture(Texture2D texture)
    2.     {
    3.         if (assetPath.ToLower().IndexOf("/spritesheets/") == -1)
    4.             return;
    5.         int minimumSpriteSize = 16;
    6.         int extrudeSize = 0;
    7.         Rect[] rects = InternalSpriteUtility.GenerateAutomaticSpriteRectangles(texture, minimumSpriteSize, extrudeSize);
    8.         var A_Sprite = rects.OrderBy(r => r.width * r.height).First().center;
    9.         int colCount = rects.Where(r => r.Contains(new Vector2(r.center.x, A_Sprite.y))).Count();
    10.         int rowCount = rects.Where(r => r.Contains(new Vector2(A_Sprite.x, r.center.y))).Count();
    11.         Vector2Int spriteSize = new Vector2Int(texture.width / colCount, texture.height / rowCount);
    12.  
    13.         List<SpriteMetaData> metas = new List<SpriteMetaData>();
    14.  
    15.         for (int r = 0; r < rowCount; ++r)
    16.         {
    17.             for (int c = 0; c < colCount; ++c)
    18.             {
    19.                 SpriteMetaData meta = new SpriteMetaData();
    20.                 meta.rect = new Rect(c * spriteSize.x, r * spriteSize.y, spriteSize.x, spriteSize.y);
    21.                 meta.name = string.Format("#{3} {0} ({1},{2})", Path.GetFileNameWithoutExtension(assetImporter.assetPath), c, r,r*colCount+c);
    22.                 metas.Add(meta);
    23.             }
    24.         }
    25.  
    26.         TextureImporter textureImporter = (TextureImporter)assetImporter;
    27.         textureImporter.spritesheet = metas.ToArray();
    28.         AssetDatabase.Refresh();
    29.     }
    if any one can see any problems let me know :)
     
    IEdge likes this.
  12. IEdge

    IEdge

    Joined:
    Mar 25, 2017
    Posts:
    45
    Unfortunately it does not work with all the sprite sheets
     
  13. KingKong320

    KingKong320

    Joined:
    Mar 2, 2018
    Posts:
    21
    when i download the image and assign it to gameobject's sprite Renderer,And after that if i quit game,where does that downloaded image goes?
    Is it in my device or have to download again on next time opening game?
     
unityunity