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.
  2. Unity 2022.2 is now available as the latest Tech release.
    Dismiss Notice
  3. We are making some changes to the DOTS forums.
    Dismiss Notice
  4. Have a look at our Games Focus blog post series which will show what Unity is doing for all game developers – now, next year, and in the future.
    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,412
    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:
    66
    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.  
     
    lofell, shshwdr, FranekKimono and 3 others like this.
  6. ChiuanWei

    ChiuanWei

    Joined:
    Jan 29, 2012
    Posts:
    126
    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.
     
    adrian_garza and chenshajie like 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:
    50
    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?