Copy spritesheet slices and pivots [Solved]

Hello everyone, a few weeks ago I was looking for a way of copying slices and pivots from one sprite sheet to another, and I could find anything but a plugin called easy sprite sheet.

So after a few days of research I just make it to work and I wanted to share the code with you.

There were two threads that helped me:

Maybe it is not the best way of doing it but could give anyone the idea if someone is struggling with this too as I was.

using UnityEngine;   
using UnityEditor;
using System.Collections;
using System.Collections.Generic;

public class MenuItems : MonoBehaviour {
   
    [MenuItem("Sprites/Paste default Slices-Pivots")]
    static void PasteDefaultSlicesPivotsCharacter()
    {
        TextureImporter defaultTextureImporter;
        string[] nameFilter = new string[1];
       
        Object[] textures = GetSelectedTextures();
        Selection.objects = new Object[0];
       
        foreach (Texture2D texture in textures)
        {
            string path = AssetDatabase.GetAssetPath(texture);
            TextureImporter ti = AssetImporter.GetAtPath(path) as TextureImporter;
            ti.isReadable = true;
           
            List<SpriteMetaData> newData = new List<SpriteMetaData>();
            for (int i = 0; i < ti.spritesheet.Length; i++)
            {
                SpriteMetaData d = ti.spritesheet[i];
                defaultTextureImporter = AssetImporter.GetAtPath("Assets/Resources/{nameofyourtemplatetexture.ext}") as TextureImporter;
                defaultTextureImporter.isReadable = true;
               
                List<SpriteMetaData> defaultData = new List<SpriteMetaData>();
                for (int j = 0; j < defaultTextureImporter.spritesheet.Length; j++)
                {
                    if(defaultTextureImporter.spritesheet[j].name.Substring(defaultTextureImporter.spritesheet[j].name.IndexOf('_'), (defaultTextureImporter.spritesheet[j].name.Length - defaultTextureImporter.spritesheet[j].name.IndexOf('_'))) ==
                       d.name.Substring(d.name.IndexOf('_'), (d.name.Length - d.name.IndexOf('_'))))
                    {
                        d.alignment = defaultTextureImporter.spritesheet[j].alignment;
                        d.border = defaultTextureImporter.spritesheet[j].border;
                        d.pivot = defaultTextureImporter.spritesheet[j].pivot;
                        d.rect = defaultTextureImporter.spritesheet[j].rect;
                        Debug.Log("Slice and pivot copied to " + d.name + " from " + defaultTextureImporter.spritesheet[j].name);
                    }
                }
                newData.Add(d);
            }
            ti.spritesheet = newData.ToArray();
            AssetDatabase.ImportAsset(path, ImportAssetOptions.ForceUpdate);
        }
    }
   
    static Object[] GetSelectedTextures()
    {
        return Selection.GetFiltered(typeof(Texture2D), SelectionMode.DeepAssets);
    }   
}

I have a default template where are my slices and pivots, so I just put the name of this default texture where it says “{nameofyourtemplatetexture.ext}”. You could also make that template selectable and not hardcoded but for what I’m doing that works. Note that for this code to work, you have to create a folder on top of the Asset folder called Editor, this code has to be inside this folder. You then will notice a new menu called Sprites on the menu bar in Unity Editor.

With the code above you can share your slices, pivots, borders etc between spritesheets, if then you want to swap sprites between this new sliced spritesheets you can use the following code:

using UnityEngine;
using System.Collections;

public class ReSkinAnimation : MonoBehaviour {

    public Sprite spriteSheet;
    private Sprite[] subSprites;
    private string currentSpriteName;
    private string newSpriteName;
    private string currentSpriteNumber;
   
    void Start () {
        if(spriteSheet == null) return;
        currentSpriteName = GetComponent<SpriteRenderer>().sprite.name.Substring(0, GetComponent<SpriteRenderer>().sprite.name.IndexOf('_'));    
        newSpriteName = spriteSheet.name.Substring(0, spriteSheet.name.IndexOf('_'));
       
        if(currentSpriteName == newSpriteName) return;
        subSprites = Resources.LoadAll<Sprite>(newSpriteName);
       
    }
   
    void LateUpdate () {   
        if(spriteSheet == null) return;
        if(currentSpriteName == spriteSheet.name) return;
       
        currentSpriteNumber = GetComponent<SpriteRenderer>().sprite.name.Substring(GetComponent<SpriteRenderer>().sprite.name.IndexOf('_')+1);//(GetComponent<SpriteRenderer>().sprite.name.Length - GetComponent<SpriteRenderer>().sprite.name.IndexOf('_')+1)));
       
        GetComponent<SpriteRenderer>().sprite = subSprites[int.Parse(currentSpriteNumber)];
    }
}

Just add this code as a component for the gameobjects that you want to swap their spritesheet, and pass to the public variable spriteSheet, the new sprite you want to use. Note that this gameobject needs to have already a default spritesheet so it can be swapped.

Hope it helps at least one you since I was not able to find too much information about this topics.

See ya.

4 Likes

Hi, I modified your code a bit and made a editor extension which lets you select the spritesheets you wish to copy from/to, just put the script in your editor folder.

using UnityEngine;
using UnityEditor;
using System.Collections.Generic;

public class SpriteCopy : EditorWindow {
  
   Object copyFrom;
   Object copyTo;

   // Creates a new option in "Windows"
   [MenuItem ("Window/Copy Spritesheet pivots and slices")]
   static void Init () {
     // Get existing open window or if none, make a new one:
     SpriteCopy window = (SpriteCopy)EditorWindow.GetWindow (typeof (SpriteCopy));
     window.Show();
   }
  
   void OnGUI () {
     GUILayout.BeginHorizontal ();
     GUILayout.Label ("Copy from:", EditorStyles.boldLabel);
     copyFrom = EditorGUILayout.ObjectField(copyFrom, typeof(Texture2D), false, GUILayout.Width(220));
     GUILayout.EndHorizontal ();

     GUILayout.BeginHorizontal ();
     GUILayout.Label ("Copy to:", EditorStyles.boldLabel);
     copyTo = EditorGUILayout.ObjectField(copyTo, typeof(Texture2D), false, GUILayout.Width(220));
     GUILayout.EndHorizontal ();
    
     GUILayout.Space (25f);
     if (GUILayout.Button ("Copy pivots and slices")) {
       CopyPivotsAndSlices();
     }
   }
  
   void CopyPivotsAndSlices()
   {
     if (!copyFrom || !copyTo) {
       Debug.Log("Missing one object");
       return;
     }
    
     if (copyFrom.GetType () != typeof(Texture2D) || copyTo.GetType () != typeof(Texture2D)) {
       Debug.Log ("Cant convert from: " + copyFrom.GetType () + "to: " + copyTo.GetType () + ". Needs two Texture2D objects!");
       return;
     }
    
     string copyFromPath = AssetDatabase.GetAssetPath(copyFrom);
     TextureImporter ti1 = AssetImporter.GetAtPath(copyFromPath) as TextureImporter;
     ti1.isReadable = true;
    
     string copyToPath = AssetDatabase.GetAssetPath(copyTo);
     TextureImporter ti2 = AssetImporter.GetAtPath(copyToPath) as TextureImporter;
     ti2.isReadable = true;

     ti2.spriteImportMode = SpriteImportMode.Multiple;

     List < SpriteMetaData > newData = new List < SpriteMetaData > ();
    
     Debug.Log ("Amount of slices found: " + ti1.spritesheet.Length);
    
     for (int i = 0; i < ti1.spritesheet.Length; i++) {
       SpriteMetaData d = ti1.spritesheet[i];
       newData.Add(d);
     }
     ti2.spritesheet = newData.ToArray();
    
     AssetDatabase.ImportAsset(copyToPath, ImportAssetOptions.ForceUpdate);
    
   }
}
9 Likes

Nice, thanks! I will give it a try :slight_smile:
Hope my code helped you too.

For anyone looking at this thread: I’ve made Easy Sprite Sheet Copy free now.

edit

Also… OP, it wasn’t like as if the plugin was expensive… At $5, most people would probably spend more money in man-hours trying to setup something similar. Anywho: It’s free now.

6 Likes

I was having issues getting this to work – for whatever reason, Unity did it once and then refused to do it again. Probably some editor bug. Instead, I just wrote a bash script to edit the meta files (the meta files must be text).

This code will bottom-center align the sprites in all meta files you pass in. Note that this is for Mac OS X. I think on Linux you would remove the ‘’ from after the sed -i. For some reason they have different parameters for sed on those OS’s. If you’re on Windows then I guess good luck? Maybe Cygwin can do this.

for f in "$@"
do
    echo "Changing pivot for $f..."
    sed -i '' 's#pivot:.*#pivot: {x: 0.5, y: 0}#g' "$f"
    echo "Changing alignment for $f..."
    sed -i '' 's#alignment:.*#alignment: 7#g' "$f"
done

Just copy/paste that into “pivotChanger.sh” or whatever you want the file to be, then in the command line:

$ chmod +x pivotChanger.sh
$ ./pivotChanger.sh file1.meta file2.meta file3.meta

(you only need to chmod once).

Normal bash rules apply, so you could also do:

$ ./pivotChanger.sh *.meta
1 Like

After some trial and error, I was able to get it to work more than once by modifying this section at line 69 of Rampe’s code. Hope it helps future Googlers.

ti2.isReadable = false;
AssetDatabase.ImportAsset(copyToPath ImportAssetOptions.ForceUpdate);
ti2.isReadable = true;
3 Likes

I know this thread is old.
But thank you guys, it saved me a lot of work!!

2 Likes

Caffeen you miss a ‘,’ in ImportAsset

  • ti2.isReadable = false;
  • AssetDatabase.ImportAsset(copyToPath, ImportAssetOptions.ForceUpdate);
  • ti2.isReadable = true;

Thanks for all by the way

Apologies if I’m digging up the dead here, but I ran into this thread as part of my research into accomplishing this task. I’ve since learned that TextureImporter.SpriteSheet is now obsolete, and will result in issues in the metadata sprite IDs if you try to simply set the spritesheet of one texture to another (as seen in the solutions above). The solution is to use the new Sprite Editor Data Provider API .

In any case… to help anyone else who runs into this (and for posterity) I’ll leave my extension script that uses this new API to copy the metadata over from one Texture2D (sprite) to another. This will copy over the splicing as well as rename the sprite rects such that the prefix is that of the destination Texture2D’s name (not the original/source).

using System.Text.RegularExpressions;
#if UNITY_EDITOR
using UnityEditor.U2D.Sprites;
using UnityEditor;
using UnityEngine;
#endif
using System.Linq;

public static class Texture2DExtensions
{

#if UNITY_EDITOR
 
    public static void CopyMetadata(this Texture2D source,
                                    Texture2D destination)
    {
            // Create Data Provider for destination texture
            var destinationFactory = new SpriteDataProviderFactories();
            destinationFactory.Init();
            var destinationDataProvider = destinationFactory.GetSpriteEditorDataProviderFromObject(destination);
            destinationDataProvider.InitSpriteEditorDataProvider();

            // Create Data Provider for source texture
            var sourceFactory = new SpriteDataProviderFactories();
            sourceFactory.Init();
            var sourceDataProvider = sourceFactory.GetSpriteEditorDataProviderFromObject(source);
            sourceDataProvider.InitSpriteEditorDataProvider();

            // Get sprite rects of the source
            SpriteRect[] sourceSpriteRects = sourceDataProvider.GetSpriteRects();

            // Create a list of indices being used
            List<int> indices = new List<int>();
            foreach (var spriteRect in sourceSpriteRects)
            {
                Regex rx = new Regex(@".*_(?<suffix>\d*)$");
                Match match = rx.Match(spriteRect.name);
                if (match.Success)
                {
                    indices.Add(int.Parse(match.Groups["suffix"].Value));
                }
            }

            // Create new SpriteRects that copy the source
            List<SpriteRect> newSpriteRects = new List<SpriteRect>();
            List<SpriteNameFileIdPair> newPairs = new List<SpriteNameFileIdPair>();
            foreach (var spriteRect in sourceSpriteRects)
            {
                // If the original naming convention of each rect is of the format <name>_<index>
                // then we want keep the suffix <index>, and replace the prefix <name>. Otherwise,
                // use the smallest missing index.
                Regex rx = new Regex(@"(?<prefix>.*)_(?<suffix>\d*)$");
                Match match = rx.Match(spriteRect.name);
                string newName;
                if (match.Success)
                {
                    newName = $"{destination.name}_{match.Groups["suffix"].Value}";
                }
                else
                {
                    int index = Enumerable.Range(0, indices.Max() + 2).Except(indices).Min();
                    newName = $"{destination.name}_{index}";
                    indices.Add(index);
                }

                // Create a new pair
                SpriteNameFileIdPair newPair = new SpriteNameFileIdPair();

                // Make a copy of the spriteRect
                SpriteRect newSpriteRect = spriteRect.Copy(newName);

                // Update pair name and GUID
                newPair.name = newName;
                newPair.SetFileGUID(newSpriteRect.spriteID);

                // Add to the new lists
                newPairs.Add(newPair);
                newSpriteRects.Add(newSpriteRect);
            }

            // Set sprite rects
            destinationDataProvider.SetSpriteRects(newSpriteRects.ToArray());

            // Set name file id pairs
            var textureNameFileIdDataProvider = destinationDataProvider.GetDataProvider<ISpriteNameFileIdDataProvider>();
            textureNameFileIdDataProvider.SetNameFileIdPairs(newPairs);

            // Apply and save
            destinationDataProvider.Apply();
            var assetImporter = destinationDataProvider.targetObject as AssetImporter;
            assetImporter.SaveAndReimport();
        }
#endif
}

8961474–1231182–Texture2DExtensions.cs (2.79 KB)

3 Likes

Editor script make copy rects from original to target multiple sprite.

using System.Text.RegularExpressions;
#if UNITY_EDITOR
using UnityEditor;
using UnityEditor.U2D.Sprites;
using UnityEngine;
using System.Collections.Generic;
using System.Linq;

public class SpriteMetadataCopier : EditorWindow
{
    private Texture2D sourceSprite;
    private Texture2D targetSprite;

    [MenuItem("Tools/Sprite Metadata Copier")]
    static void Init()
    {
        var window = GetWindow<SpriteMetadataCopier>();
        window.Show();
    }

    void OnGUI()
    {
        sourceSprite = (Texture2D)EditorGUILayout.ObjectField("Source Sprite", sourceSprite, typeof(Texture2D), false);
        targetSprite = (Texture2D)EditorGUILayout.ObjectField("Target Sprite", targetSprite, typeof(Texture2D), false);

        if (GUILayout.Button("Copy Metadata"))
        {
            if (sourceSprite != null && targetSprite != null)
            {
                sourceSprite.CopyMetadata(targetSprite);
            }
        }
    }
}

public static class Texture2DExtensions
{
    public static void CopyMetadata(this Texture2D source, Texture2D destination)
    {
        // Create Data Provider for destination texture
        var destinationFactory = new SpriteDataProviderFactories();
        destinationFactory.Init();
        var destinationDataProvider = destinationFactory.GetSpriteEditorDataProviderFromObject(destination);
        destinationDataProvider.InitSpriteEditorDataProvider();

        // Create Data Provider for source texture
        var sourceFactory = new SpriteDataProviderFactories();
        sourceFactory.Init();
        var sourceDataProvider = sourceFactory.GetSpriteEditorDataProviderFromObject(source);
        sourceDataProvider.InitSpriteEditorDataProvider();

        // Get sprite rects of the source
        SpriteRect[] sourceSpriteRects = sourceDataProvider.GetSpriteRects();

        // Create a list of indices being used
        List<int> indices = new List<int>();
        foreach (var spriteRect in sourceSpriteRects)
        {
            Regex rx = new Regex(@".*_(?<suffix>\d*)$");
            Match match = rx.Match(spriteRect.name);
            if (match.Success)
            {
                indices.Add(int.Parse(match.Groups["suffix"].Value));
            }
        }

        // Create new SpriteRects that copy the source
        List<SpriteRect> newSpriteRects = new List<SpriteRect>();
        List<SpriteNameFileIdPair> newPairs = new List<SpriteNameFileIdPair>();
        foreach (var spriteRect in sourceSpriteRects)
        {
            Regex rx = new Regex(@"(?<prefix>.*)_(?<suffix>\d*)$");
            Match match = rx.Match(spriteRect.name);
            string newName;
            if (match.Success)
            {
                newName = $"{destination.name}_{match.Groups["suffix"].Value}";
            }
            else
            {
                int index = Enumerable.Range(0, indices.Max() + 2).Except(indices).Min();
                newName = $"{destination.name}_{index}";
                indices.Add(index);
            }

            // Create a new pair
            SpriteNameFileIdPair newPair = new SpriteNameFileIdPair();

            // Make a copy of the spriteRect
            SpriteRect newSpriteRect = spriteRect.Copy();

            // Update pair name and GUID
            newPair.name = newName;
            newPair.SetFileGUID(newSpriteRect.spriteID);

            // Add to the new lists
            newPairs.Add(newPair);
            newSpriteRects.Add(newSpriteRect);
        }

        // Set sprite rects
        destinationDataProvider.SetSpriteRects(newSpriteRects.ToArray());

        // Set name file id pairs
        var textureNameFileIdDataProvider = destinationDataProvider.GetDataProvider<ISpriteNameFileIdDataProvider>();
        textureNameFileIdDataProvider.SetNameFileIdPairs(newPairs);

        // Apply and save
        destinationDataProvider.Apply();
        var assetImporter = destinationDataProvider.targetObject as AssetImporter;
        assetImporter.SaveAndReimport();
    }
}

#endif
public static class SpriteRectExtensions
{
    public static SpriteRect Copy(this SpriteRect original, string newName = null)
    {
        SpriteRect copy = new SpriteRect
        {
            name = newName ?? original.name, // Используйте новое имя, если оно предоставлено, иначе используйте оригинальное имя
            rect = original.rect,
            alignment = original.alignment,
            border = original.border,
            pivot = original.pivot,
            spriteID = original.spriteID // Ensure this is handled correctly
        };

        return copy;
    }
}
1 Like