Search Unity

Copy spritesheet slices and pivots [Solved]

Discussion in '2D' started by angelsolares, Feb 25, 2015.

  1. angelsolares

    angelsolares

    Joined:
    Nov 9, 2013
    Posts:
    3
    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:

    http://answers.unity3d.com/questions/761009/unity2d-sprite-editor-multiple-pivot-point-edit.html

    http://forum.unity3d.com/threads/swapping-out-sprite-resources-in-order-to-reuse-animations.273103/

    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.

    Code (CSharp):
    1.  
    2. using UnityEngine;  
    3. using UnityEditor;
    4. using System.Collections;
    5. using System.Collections.Generic;
    6.  
    7. public class MenuItems : MonoBehaviour {
    8.    
    9.     [MenuItem("Sprites/Paste default Slices-Pivots")]
    10.     static void PasteDefaultSlicesPivotsCharacter()
    11.     {
    12.         TextureImporter defaultTextureImporter;
    13.         string[] nameFilter = new string[1];
    14.        
    15.         Object[] textures = GetSelectedTextures();
    16.         Selection.objects = new Object[0];
    17.        
    18.         foreach (Texture2D texture in textures)
    19.         {
    20.             string path = AssetDatabase.GetAssetPath(texture);
    21.             TextureImporter ti = AssetImporter.GetAtPath(path) as TextureImporter;
    22.             ti.isReadable = true;
    23.            
    24.             List<SpriteMetaData> newData = new List<SpriteMetaData>();
    25.             for (int i = 0; i < ti.spritesheet.Length; i++)
    26.             {
    27.                 SpriteMetaData d = ti.spritesheet[i];
    28.                 defaultTextureImporter = AssetImporter.GetAtPath("Assets/Resources/{nameofyourtemplatetexture.ext}") as TextureImporter;
    29.                 defaultTextureImporter.isReadable = true;
    30.                
    31.                 List<SpriteMetaData> defaultData = new List<SpriteMetaData>();
    32.                 for (int j = 0; j < defaultTextureImporter.spritesheet.Length; j++)
    33.                 {
    34.                     if(defaultTextureImporter.spritesheet[j].name.Substring(defaultTextureImporter.spritesheet[j].name.IndexOf('_'), (defaultTextureImporter.spritesheet[j].name.Length - defaultTextureImporter.spritesheet[j].name.IndexOf('_'))) ==
    35.                        d.name.Substring(d.name.IndexOf('_'), (d.name.Length - d.name.IndexOf('_'))))
    36.                     {
    37.                         d.alignment = defaultTextureImporter.spritesheet[j].alignment;
    38.                         d.border = defaultTextureImporter.spritesheet[j].border;
    39.                         d.pivot = defaultTextureImporter.spritesheet[j].pivot;
    40.                         d.rect = defaultTextureImporter.spritesheet[j].rect;
    41.                         Debug.Log("Slice and pivot copied to " + d.name + " from " + defaultTextureImporter.spritesheet[j].name);
    42.                     }
    43.                 }
    44.                 newData.Add(d);
    45.             }
    46.             ti.spritesheet = newData.ToArray();
    47.             AssetDatabase.ImportAsset(path, ImportAssetOptions.ForceUpdate);
    48.         }
    49.     }
    50.    
    51.     static Object[] GetSelectedTextures()
    52.     {
    53.         return Selection.GetFiltered(typeof(Texture2D), SelectionMode.DeepAssets);
    54.     }  
    55. }
    56.  
    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:

    Code (CSharp):
    1.  
    2. using UnityEngine;
    3. using System.Collections;
    4.  
    5. public class ReSkinAnimation : MonoBehaviour {
    6.  
    7.     public Sprite spriteSheet;
    8.     private Sprite[] subSprites;
    9.     private string currentSpriteName;
    10.     private string newSpriteName;
    11.     private string currentSpriteNumber;
    12.    
    13.     void Start () {
    14.         if(spriteSheet == null) return;
    15.         currentSpriteName = GetComponent<SpriteRenderer>().sprite.name.Substring(0, GetComponent<SpriteRenderer>().sprite.name.IndexOf('_'));    
    16.         newSpriteName = spriteSheet.name.Substring(0, spriteSheet.name.IndexOf('_'));
    17.        
    18.         if(currentSpriteName == newSpriteName) return;
    19.         subSprites = Resources.LoadAll<Sprite>(newSpriteName);
    20.        
    21.     }
    22.    
    23.     void LateUpdate () {  
    24.         if(spriteSheet == null) return;
    25.         if(currentSpriteName == spriteSheet.name) return;
    26.        
    27.         currentSpriteNumber = GetComponent<SpriteRenderer>().sprite.name.Substring(GetComponent<SpriteRenderer>().sprite.name.IndexOf('_')+1);//(GetComponent<SpriteRenderer>().sprite.name.Length - GetComponent<SpriteRenderer>().sprite.name.IndexOf('_')+1)));
    28.        
    29.         GetComponent<SpriteRenderer>().sprite = subSprites[int.Parse(currentSpriteNumber)];
    30.     }
    31. }
    32.  
    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.
     
  2. Rampe

    Rampe

    Joined:
    Jul 20, 2014
    Posts:
    1
    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.



    Code (CSharp):
    1.  
    2.  
    3. using UnityEngine;
    4. using UnityEditor;
    5. using System.Collections.Generic;
    6.  
    7. public class SpriteCopy : EditorWindow {
    8.  
    9.    Object copyFrom;
    10.    Object copyTo;
    11.  
    12.    // Creates a new option in "Windows"
    13.    [MenuItem ("Window/Copy Spritesheet pivots and slices")]
    14.    static void Init () {
    15.      // Get existing open window or if none, make a new one:
    16.      SpriteCopy window = (SpriteCopy)EditorWindow.GetWindow (typeof (SpriteCopy));
    17.      window.Show();
    18.    }
    19.  
    20.    void OnGUI () {
    21.      GUILayout.BeginHorizontal ();
    22.      GUILayout.Label ("Copy from:", EditorStyles.boldLabel);
    23.      copyFrom = EditorGUILayout.ObjectField(copyFrom, typeof(Texture2D), false, GUILayout.Width(220));
    24.      GUILayout.EndHorizontal ();
    25.  
    26.      GUILayout.BeginHorizontal ();
    27.      GUILayout.Label ("Copy to:", EditorStyles.boldLabel);
    28.      copyTo = EditorGUILayout.ObjectField(copyTo, typeof(Texture2D), false, GUILayout.Width(220));
    29.      GUILayout.EndHorizontal ();
    30.    
    31.      GUILayout.Space (25f);
    32.      if (GUILayout.Button ("Copy pivots and slices")) {
    33.        CopyPivotsAndSlices();
    34.      }
    35.    }
    36.  
    37.    void CopyPivotsAndSlices()
    38.    {
    39.      if (!copyFrom || !copyTo) {
    40.        Debug.Log("Missing one object");
    41.        return;
    42.      }
    43.    
    44.      if (copyFrom.GetType () != typeof(Texture2D) || copyTo.GetType () != typeof(Texture2D)) {
    45.        Debug.Log ("Cant convert from: " + copyFrom.GetType () + "to: " + copyTo.GetType () + ". Needs two Texture2D objects!");
    46.        return;
    47.      }
    48.    
    49.      string copyFromPath = AssetDatabase.GetAssetPath(copyFrom);
    50.      TextureImporter ti1 = AssetImporter.GetAtPath(copyFromPath) as TextureImporter;
    51.      ti1.isReadable = true;
    52.    
    53.      string copyToPath = AssetDatabase.GetAssetPath(copyTo);
    54.      TextureImporter ti2 = AssetImporter.GetAtPath(copyToPath) as TextureImporter;
    55.      ti2.isReadable = true;
    56.  
    57.      ti2.spriteImportMode = SpriteImportMode.Multiple;
    58.  
    59.      List < SpriteMetaData > newData = new List < SpriteMetaData > ();
    60.    
    61.      Debug.Log ("Amount of slices found: " + ti1.spritesheet.Length);
    62.    
    63.      for (int i = 0; i < ti1.spritesheet.Length; i++) {
    64.        SpriteMetaData d = ti1.spritesheet[i];
    65.        newData.Add(d);
    66.      }
    67.      ti2.spritesheet = newData.ToArray();
    68.    
    69.      AssetDatabase.ImportAsset(copyToPath, ImportAssetOptions.ForceUpdate);
    70.    
    71.    }
    72. }
    73.  
    74.  
     
  3. angelsolares

    angelsolares

    Joined:
    Nov 9, 2013
    Posts:
    3
    Nice, thanks! I will give it a try :)
    Hope my code helped you too.
     
  4. Guidez

    Guidez

    Joined:
    Feb 9, 2013
    Posts:
    6
    For anyone looking at this thread: I've made Easy Sprite Sheet Copy free now.

    https://www.assetstore.unity3d.com/en/#!/content/27435

    *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.
     
    Bralgs, nc3, Caffeen and 3 others like this.
  5. demonpants

    demonpants

    Joined:
    Oct 3, 2008
    Posts:
    82
    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.
    Code (sh):
    1. for f in "$@"
    2. do
    3.     echo "Changing pivot for $f..."
    4.     sed -i '' 's#pivot:.*#pivot: {x: 0.5, y: 0}#g' "$f"
    5.     echo "Changing alignment for $f..."
    6.     sed -i '' 's#alignment:.*#alignment: 7#g' "$f"
    7. done
    Just copy/paste that into "pivotChanger.sh" or whatever you want the file to be, then in the command line:
    Code (sh):
    1. $ chmod +x pivotChanger.sh
    2. $ ./pivotChanger.sh file1.meta file2.meta file3.meta
    (you only need to chmod once).

    Normal bash rules apply, so you could also do:
    Code (sh):
    1. $ ./pivotChanger.sh *.meta
     
    austinborden likes this.
  6. Caffeen

    Caffeen

    Joined:
    Dec 25, 2013
    Posts:
    34
    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.

    Code (CSharp):
    1. ti2.isReadable = false;
    2. AssetDatabase.ImportAsset(copyToPath ImportAssetOptions.ForceUpdate);
    3. ti2.isReadable = true;
     
    Jiaquarium, connorjarnagin and R4vakk like this.
  7. AleNavarro

    AleNavarro

    Joined:
    Dec 30, 2016
    Posts:
    7
    I know this thread is old.
    But thank you guys, it saved me a lot of work!!
     
    Pladevall and Bralgs like this.
  8. Docboy

    Docboy

    Joined:
    Sep 11, 2013
    Posts:
    14
    Caffeen you miss a ',' in ImportAsset
    1. ti2.isReadable = false;
    2. AssetDatabase.ImportAsset(copyToPath, ImportAssetOptions.ForceUpdate);
    3. ti2.isReadable = true;
    Thanks for all by the way
     
  9. ShervinM

    ShervinM

    Joined:
    Sep 16, 2017
    Posts:
    67
    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).


    Code (CSharp):
    1. using System.Text.RegularExpressions;
    2. #if UNITY_EDITOR
    3. using UnityEditor.U2D.Sprites;
    4. using UnityEditor;
    5. using UnityEngine;
    6. #endif
    7. using System.Linq;
    8.  
    9. public static class Texture2DExtensions
    10. {
    11.  
    12. #if UNITY_EDITOR
    13.  
    14.     public static void CopyMetadata(this Texture2D source,
    15.                                     Texture2D destination)
    16.     {
    17.             // Create Data Provider for destination texture
    18.             var destinationFactory = new SpriteDataProviderFactories();
    19.             destinationFactory.Init();
    20.             var destinationDataProvider = destinationFactory.GetSpriteEditorDataProviderFromObject(destination);
    21.             destinationDataProvider.InitSpriteEditorDataProvider();
    22.  
    23.             // Create Data Provider for source texture
    24.             var sourceFactory = new SpriteDataProviderFactories();
    25.             sourceFactory.Init();
    26.             var sourceDataProvider = sourceFactory.GetSpriteEditorDataProviderFromObject(source);
    27.             sourceDataProvider.InitSpriteEditorDataProvider();
    28.  
    29.             // Get sprite rects of the source
    30.             SpriteRect[] sourceSpriteRects = sourceDataProvider.GetSpriteRects();
    31.  
    32.             // Create a list of indices being used
    33.             List<int> indices = new List<int>();
    34.             foreach (var spriteRect in sourceSpriteRects)
    35.             {
    36.                 Regex rx = new Regex(@".*_(?<suffix>\d*)$");
    37.                 Match match = rx.Match(spriteRect.name);
    38.                 if (match.Success)
    39.                 {
    40.                     indices.Add(int.Parse(match.Groups["suffix"].Value));
    41.                 }
    42.             }
    43.  
    44.             // Create new SpriteRects that copy the source
    45.             List<SpriteRect> newSpriteRects = new List<SpriteRect>();
    46.             List<SpriteNameFileIdPair> newPairs = new List<SpriteNameFileIdPair>();
    47.             foreach (var spriteRect in sourceSpriteRects)
    48.             {
    49.                 // If the original naming convention of each rect is of the format <name>_<index>
    50.                 // then we want keep the suffix <index>, and replace the prefix <name>. Otherwise,
    51.                 // use the smallest missing index.
    52.                 Regex rx = new Regex(@"(?<prefix>.*)_(?<suffix>\d*)$");
    53.                 Match match = rx.Match(spriteRect.name);
    54.                 string newName;
    55.                 if (match.Success)
    56.                 {
    57.                     newName = $"{destination.name}_{match.Groups["suffix"].Value}";
    58.                 }
    59.                 else
    60.                 {
    61.                     int index = Enumerable.Range(0, indices.Max() + 2).Except(indices).Min();
    62.                     newName = $"{destination.name}_{index}";
    63.                     indices.Add(index);
    64.                 }
    65.  
    66.                 // Create a new pair
    67.                 SpriteNameFileIdPair newPair = new SpriteNameFileIdPair();
    68.  
    69.                 // Make a copy of the spriteRect
    70.                 SpriteRect newSpriteRect = spriteRect.Copy(newName);
    71.  
    72.                 // Update pair name and GUID
    73.                 newPair.name = newName;
    74.                 newPair.SetFileGUID(newSpriteRect.spriteID);
    75.  
    76.                 // Add to the new lists
    77.                 newPairs.Add(newPair);
    78.                 newSpriteRects.Add(newSpriteRect);
    79.             }
    80.  
    81.             // Set sprite rects
    82.             destinationDataProvider.SetSpriteRects(newSpriteRects.ToArray());
    83.  
    84.             // Set name file id pairs
    85.             var textureNameFileIdDataProvider = destinationDataProvider.GetDataProvider<ISpriteNameFileIdDataProvider>();
    86.             textureNameFileIdDataProvider.SetNameFileIdPairs(newPairs);
    87.  
    88.             // Apply and save
    89.             destinationDataProvider.Apply();
    90.             var assetImporter = destinationDataProvider.targetObject as AssetImporter;
    91.             assetImporter.SaveAndReimport();
    92.         }
    93. #endif
    94. }
    95.  
     

    Attached Files:

    Last edited: Aug 15, 2023
    Crokett and Ted_Wikman like this.