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?
Hi,
You can implement Unity - Scripting API: AssetPostprocessor.OnPostprocessTexture(Texture2D) and define whatever Unity - Scripting API: TextureImporter.spritesheet you need.
Thanks, but I was hoping for a way to set the SpriteMetaData through Unity’s method of slicing or is that not accessible?
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:
- 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.
- 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
- OnPostprocessSprites() → This should result in the sliced sprites as the second param.
public class SpriteProcessor : AssetPostprocessor
{
void OnPreprocessTexture ()
{
TextureImporter textureImporter = (TextureImporter)assetImporter;
textureImporter.textureType = TextureImporterType.Sprite;
textureImporter.spriteImportMode = SpriteImportMode.Multiple;
textureImporter.mipmapEnabled = false;
textureImporter.filterMode = FilterMode.Point;
}
public void OnPostprocessTexture (Texture2D texture)
{
Debug.Log("Texture2D: (" + texture.width + "x" + texture.height + ")");
int spriteSize = 64;
int colCount = texture.width / spriteSize;
int rowCount = texture.height / spriteSize;
List<SpriteMetaData> metas = new List<SpriteMetaData>();
for (int r = 0; r < rowCount; ++r)
{
for (int c = 0; c < colCount; ++c)
{
SpriteMetaData meta = new SpriteMetaData();
meta.rect = new Rect(c * spriteSize, r * spriteSize, spriteSize, spriteSize);
meta.name = c + "-" + r;
metas.Add(meta);
}
}
TextureImporter textureImporter = (TextureImporter)assetImporter;
textureImporter.spritesheet = metas.ToArray();
}
public void OnPostprocessSprites(Texture2D texture, Sprite[] sprites)
{
Debug.Log("Sprites: " + sprites.Length);
}
}
You can now (Unity 2017.3) access InternalSpriteUtility.GenerateAutomaticSpriteRectangles to automatically slice up a texture into sprites during OnPostprocessTexture. Code:
using UnityEngine;
using UnityEditor;
using System.Collections.Generic;
using UnityEditorInternal;
using System.IO;
// https://discussions.unity.com/t/577630
// http://www.sarpersoher.com/a-custom-asset-importer-for-unity/
public class SpritePostprocessor : AssetPostprocessor {
/// <summary>
/// Default all textures to 2D sprites, pivot at bottom, mip-mapped, uncompressed.
/// </summary>
private void OnPreprocessTexture() {
Debug.Log("OnPreprocessTexture overwriting defaults");
TextureImporter importer = assetImporter as TextureImporter;
importer.textureType = TextureImporterType.Sprite;
importer.spriteImportMode = SpriteImportMode.Multiple;
importer.mipmapEnabled = true;
importer.filterMode = FilterMode.Trilinear;
importer.textureCompression = TextureImporterCompression.Uncompressed;
}
public void OnPostprocessTexture(Texture2D texture) {
TextureImporter importer = assetImporter as TextureImporter;
if (importer.spriteImportMode != SpriteImportMode.Multiple) {
return;
}
Debug.Log("OnPostprocessTexture generating sprites");
int minimumSpriteSize = 16;
int extrudeSize = 0;
Rect[] rects = InternalSpriteUtility.GenerateAutomaticSpriteRectangles(texture, minimumSpriteSize, extrudeSize);
List<Rect> rectsList = new List<Rect>(rects);
rectsList = SortRects(rectsList, texture.width);
string filenameNoExtension = Path.GetFileNameWithoutExtension(assetPath);
List<SpriteMetaData> metas = new List<SpriteMetaData>();
int rectNum = 0;
foreach (Rect rect in rectsList) {
SpriteMetaData meta = new SpriteMetaData();
meta.rect = rect;
meta.name = filenameNoExtension + "_" + rectNum++;
metas.Add(meta);
}
importer.spritesheet = metas.ToArray();
}
public void OnPostprocessSprites(Texture2D texture, Sprite[] sprites) {
Debug.Log("OnPostprocessSprites sprites: " + sprites.Length);
}
private List<Rect> SortRects(List<Rect> rects, float textureWidth) {
List<Rect> list = new List<Rect>();
while (rects.Count > 0) {
Rect rect = rects[rects.Count - 1];
Rect sweepRect = new Rect(0f, rect.yMin, textureWidth, rect.height);
List<Rect> list2 = this.RectSweep(rects, sweepRect);
if (list2.Count <= 0) {
list.AddRange(rects);
break;
}
list.AddRange(list2);
}
return list;
}
private List<Rect> RectSweep(List<Rect> rects, Rect sweepRect) {
List<Rect> result;
if (rects == null || rects.Count == 0) {
result = new List<Rect>();
} else {
List<Rect> list = new List<Rect>();
foreach (Rect current in rects) {
if (current.Overlaps(sweepRect)) {
list.Add(current);
}
}
foreach (Rect current2 in list) {
rects.Remove(current2);
}
list.Sort((Rect a, Rect b) => a.x.CompareTo(b.x));
result = list;
}
return result;
}
}
not good. and how to call the sprite editor slice function
@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.
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 )
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.
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.
public void OnPostprocessTexture(Texture2D texture)
{
if (assetPath.ToLower().IndexOf("/spritesheets/") == -1)
return;
int minimumSpriteSize = 16;
int extrudeSize = 0;
Rect[] rects = InternalSpriteUtility.GenerateAutomaticSpriteRectangles(texture, minimumSpriteSize, extrudeSize);
var A_Sprite = rects.OrderBy(r => r.width * r.height).First().center;
int colCount = rects.Where(r => r.Contains(new Vector2(r.center.x, A_Sprite.y))).Count();
int rowCount = rects.Where(r => r.Contains(new Vector2(A_Sprite.x, r.center.y))).Count();
Vector2Int spriteSize = new Vector2Int(texture.width / colCount, texture.height / rowCount);
List<SpriteMetaData> metas = new List<SpriteMetaData>();
for (int r = 0; r < rowCount; ++r)
{
for (int c = 0; c < colCount; ++c)
{
SpriteMetaData meta = new SpriteMetaData();
meta.rect = new Rect(c * spriteSize.x, r * spriteSize.y, spriteSize.x, spriteSize.y);
meta.name = string.Format("#{3} {0} ({1},{2})", Path.GetFileNameWithoutExtension(assetImporter.assetPath), c, r,r*colCount+c);
metas.Add(meta);
}
}
TextureImporter textureImporter = (TextureImporter)assetImporter;
textureImporter.spritesheet = metas.ToArray();
AssetDatabase.Refresh();
}
if any one can see any problems let me know
Unfortunately it does not work with all the sprite sheets
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?
In Unity 2020+ none of the above methods will work. The Slice feature is a bit more involved now and uses some previously undocumented parts of Unity (Unity - Manual: Sprite Editor Data Provider API).
This is what I ended up writing to get this working (Hope this helps someone else in the future):
public static Sprite[] SliceTexture(Texture2D texture, int sliceWidth, int sliceHeight)
{
List<Sprite> slicedSprites = new List<Sprite>();
string assetPath = AssetDatabase.GetAssetPath(texture);
TextureImporter textureImporter = AssetImporter.GetAtPath(assetPath) as TextureImporter;
if (textureImporter == null)
{
Debug.LogError("Failed to get TextureImporter for texture");
}
else
{
// FIRST STEP:
// Setup the texture asset to have the correct import settings
// Eg, Ensure that the texture is set to 'multiple' mode
textureImporter.textureType = TextureImporterType.Sprite;
textureImporter.spriteImportMode = SpriteImportMode.Multiple;
textureImporter.spritePixelsPerUnit = 16;
textureImporter.mipmapEnabled = false; // Mipmaps are unnecessary for sprites
textureImporter.textureCompression = TextureImporterCompression.Uncompressed;
textureImporter.maxTextureSize = 8192; // Im setting this much larger incase we have very large spritesheets
// Reimport the texture with updated settings
AssetDatabase.ImportAsset(assetPath, ImportAssetOptions.ForceUpdate);
//-----------------------------------
// SECOND STEP:
// Slice the texture and create SpriteRects that represent each sliced sprite
// Note: TextureImporter.spritesheet no longer works, it seems to get overridden
var factory = new SpriteDataProviderFactories();
factory.Init();
ISpriteEditorDataProvider dataProvider = factory.GetSpriteEditorDataProviderFromObject(texture);
dataProvider.InitSpriteEditorDataProvider();
dataProvider.SetSpriteRects(GenerateSpriteRectData(texture.width, texture.height, sliceWidth, sliceHeight));
dataProvider.Apply();
var assetImporter = dataProvider.targetObject as AssetImporter;
assetImporter.SaveAndReimport();
//------------------------------------
// THIRD STEP:
// I use this to load all of the sliced sprites into a sprite array that I use in a scriptable object
Object[] assets = AssetDatabase.LoadAllAssetsAtPath(assetPath);
foreach (Object asset in assets)
{
if (asset is Sprite sprite && sprite.name != texture.name)
{
slicedSprites.Add(sprite);
}
}
}
return slicedSprites.ToArray();
}
private static SpriteRect[] GenerateSpriteRectData(int textureWidth, int textureHeight, int sliceWidth, int sliceHeight)
{
List<SpriteRect> spriteRects = new List<SpriteRect>();
for (int y = textureHeight; y > 0; y -= sliceHeight)
{
for (int x = 0; x < textureWidth; x += sliceWidth)
{
SpriteRect spriteRect = new SpriteRect();
spriteRect.rect = new Rect(x, y - sliceHeight, sliceWidth, sliceHeight);
spriteRect.pivot = new Vector2(0.5f, 0f);
spriteRect.name = $"Slice_{x / sliceWidth}_{y / sliceHeight}";
spriteRect.alignment = SpriteAlignment.BottomCenter;
spriteRect.border = new Vector4(0,0,0,0);
spriteRects.Add(spriteRect);
}
}
return spriteRects.ToArray();
}
This method takes a texture and the width/height to grid slice the texture. It then loads all the cut sprites and returns them as an array (You might not need this part, but I used it in my case to assign it to a scriptable object)