├── .gitignore ├── README.md ├── TextureAtlasSlicer-Cocos2d-x └── Editor │ └── Cocos2dxTextureAtlasSlicer.cs └── art ├── intro.png └── menu.png /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Unity3D-TextureAtlasSlicer-Cocos2d-x 2 | Simple and fast tool to import Cocos2d-x spritesheets (TextureAtlas) into Unity3D! *This project is base on the [Unity3D-TextureAtlasSlicer](https://github.com/toxicFork/Unity3D-TextureAtlasSlicer)*. 3 | 4 | ![](art/intro.png) 5 | 6 | ## Usage 7 | - Copy the `TextureAtlasSlicer-Cocos2d-x` folder to your project's assets directory. 8 | - Import the sprite and the plist file (with the same name as the sprite) to your project's Asset. 9 | - Right click on the sprite you want to slice and select the `Slice Sprite Using Coscos2d-x Plist` menu item. ![](art/menu.png) 10 | - Configure the pivot settings. 11 | - Hit `Slice`! 12 | -------------------------------------------------------------------------------- /TextureAtlasSlicer-Cocos2d-x/Editor/Cocos2dxTextureAtlasSlicer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Xml; 6 | using UnityEditor; 7 | using UnityEngine; 8 | 9 | public class Cocos2dxTextureAtlasSlicer : EditorWindow 10 | { 11 | [MenuItem("Assets/Slice Sprite Using Coscos2d-x Plist")] 12 | public static void TextureAtlasSlicerWindow() 13 | { 14 | CreateInstance().Show(); 15 | } 16 | 17 | [MenuItem("Assets/Slice Sprite Using Coscos2d-x Plist", true)] 18 | public static bool ValidateTextureAtlasSlicerWindow() 19 | { 20 | var assetPath = GetTexturePathIfPlistExists(GetSelectedTexture()); 21 | 22 | if (assetPath != null) 23 | { 24 | var textureImporter = AssetImporter.GetAtPath(assetPath) as TextureImporter; 25 | return textureImporter && textureImporter.textureType == TextureImporterType.Sprite || 26 | textureImporter.textureType == TextureImporterType.Advanced; 27 | } 28 | 29 | // TODO: 批处理纹理 30 | return false; 31 | } 32 | 33 | private static Texture2D GetSelectedTexture() 34 | { 35 | if (Selection.objects.Length == 1 && Selection.activeObject is Texture2D) 36 | { 37 | return Selection.activeObject as Texture2D; 38 | } 39 | return null; 40 | } 41 | 42 | private static string GetTexturePathIfPlistExists(Texture2D selectedTexture, Action doIfPlistExists = null) 43 | { 44 | if (selectedTexture == null) return null; 45 | 46 | var assetPath = AssetDatabase.GetAssetPath(selectedTexture); 47 | var extension = Path.GetExtension(assetPath); 48 | var pathWithoutExtension = assetPath.Remove(assetPath.Length - extension.Length, extension.Length); 49 | var plistPath = pathWithoutExtension + ".plist"; 50 | 51 | if (File.Exists(plistPath)) 52 | { 53 | if (doIfPlistExists != null) doIfPlistExists.Invoke(plistPath); 54 | return assetPath; 55 | } 56 | return null; 57 | } 58 | 59 | public TextureImporter importer; 60 | private Texture2D selectedTexture; 61 | public SpriteAlignment spriteAlignment = SpriteAlignment.Center; 62 | public Vector2 customOffset = new Vector2(0.5f, 0.5f); 63 | private string plistContent; 64 | 65 | public Cocos2dxTextureAtlasSlicer() 66 | { 67 | titleContent = new GUIContent("Plist Slicer"); 68 | } 69 | 70 | public void OnSelectionChange() 71 | { 72 | UseSelectedTexture(); 73 | } 74 | 75 | private void UseSelectedTexture() 76 | { 77 | selectedTexture = GetSelectedTexture(); 78 | var assetPath = GetTexturePathIfPlistExists(selectedTexture, (plistPath) => 79 | { 80 | using (FileStream file = new FileStream(plistPath, FileMode.Open)) 81 | { 82 | byte[] byteArray = new byte[(int)file.Length]; 83 | file.Read(byteArray, 0, byteArray.Length); 84 | plistContent = System.Text.Encoding.Default.GetString(byteArray); 85 | file.Close(); 86 | file.Dispose(); 87 | } 88 | }); 89 | 90 | importer = AssetImporter.GetAtPath(assetPath) as TextureImporter; 91 | 92 | if (importer != null) 93 | { 94 | ParseXML(); 95 | } 96 | else 97 | { 98 | subTextures = null; 99 | } 100 | 101 | Repaint(); 102 | } 103 | 104 | private SubTexture[] subTextures; 105 | private int wantedWidth, wantedHeight; 106 | 107 | private void ParseXML() 108 | { 109 | try 110 | { 111 | var document = new XmlDocument(); 112 | document.LoadXml(plistContent); 113 | XmlNodeList frames = new PlistFinder(document.DocumentElement.ChildNodes[0]).FindValueByKey("frames").ChildNodes; 114 | 115 | ArrayList subTexs = new ArrayList(); 116 | for (int i = 0; i < frames.Count; i++) 117 | { 118 | if (frames[i].Name.ToLower() == "key") 119 | { 120 | SubTexture subTex = new SubTexture(); 121 | subTex.name = frames[i].InnerText; 122 | 123 | PlistFinder finder = new PlistFinder(frames[++i]); 124 | XmlNode rotatedNode = finder.FindValueByKey("textureRotated"); 125 | rotatedNode = rotatedNode ?? finder.FindValueByKey("rotated"); 126 | bool isRotated = (rotatedNode.Name.ToLower() == "true"); 127 | 128 | XmlNode rectNode = finder.FindValueByKey("textureRect"); 129 | rectNode = rectNode ?? finder.FindValueByKey("frame"); 130 | string rect = rectNode.InnerText; 131 | 132 | var ints = rect.Replace('{', ' ').Replace('}', ' ').Split(new char[] { ',' }) 133 | .Select(num => Int32.Parse(num.Trim())).ToArray(); 134 | 135 | subTex.width = isRotated ? ints[3] : ints[2]; 136 | subTex.height = isRotated ? ints[2] : ints[3]; 137 | subTex.x = ints[0]; 138 | subTex.y = ints[1]; 139 | subTexs.Add(subTex); 140 | } 141 | } 142 | 143 | subTextures = subTexs.Cast().ToArray(); 144 | 145 | wantedWidth = 0; 146 | wantedHeight = 0; 147 | 148 | foreach (var subTexture in subTextures) 149 | { 150 | var right = subTexture.x + subTexture.width; 151 | var bottom = subTexture.y + subTexture.height; 152 | 153 | wantedWidth = Mathf.Max(wantedWidth, right); 154 | wantedHeight = Mathf.Max(wantedHeight, bottom); 155 | } 156 | } 157 | catch (Exception) 158 | { 159 | subTextures = null; 160 | } 161 | } 162 | 163 | public void OnEnable() 164 | { 165 | UseSelectedTexture(); 166 | } 167 | 168 | public void OnGUI() 169 | { 170 | if (importer == null) 171 | { 172 | EditorGUILayout.LabelField("Please select a texture to slice."); 173 | return; 174 | } 175 | EditorGUI.BeginDisabledGroup(focusedWindow != this); 176 | { 177 | EditorGUI.BeginDisabledGroup(true); 178 | EditorGUILayout.ObjectField("Texture", Selection.activeObject, typeof(Texture), false); 179 | EditorGUI.EndDisabledGroup(); 180 | 181 | if (importer.textureType != TextureImporterType.Sprite && 182 | importer.textureType != TextureImporterType.Advanced) 183 | { 184 | EditorGUILayout.LabelField("The Texture Type needs to be Sprite or Advanced!"); 185 | } 186 | 187 | EditorGUI.BeginDisabledGroup((importer.textureType != TextureImporterType.Sprite && 188 | importer.textureType != TextureImporterType.Advanced)); 189 | { 190 | spriteAlignment = (SpriteAlignment)EditorGUILayout.EnumPopup("Pivot", spriteAlignment); 191 | 192 | EditorGUI.BeginDisabledGroup(spriteAlignment != SpriteAlignment.Custom); 193 | EditorGUILayout.Vector2Field("Custom Offset", customOffset); 194 | EditorGUI.EndDisabledGroup(); 195 | 196 | var needsToResizeTexture = wantedWidth > selectedTexture.width || wantedHeight > selectedTexture.height; 197 | 198 | if (needsToResizeTexture) 199 | { 200 | EditorGUILayout.LabelField( 201 | string.Format("Texture size too small." 202 | + " It needs to be at least {0} by {1} pixels!", 203 | wantedWidth, 204 | wantedHeight)); 205 | EditorGUILayout.LabelField("Try changing the Max Size property in the importer."); 206 | } 207 | 208 | if (subTextures == null || subTextures.Length == 0) 209 | { 210 | EditorGUILayout.LabelField("Could not find any SubTextures in XML."); 211 | } 212 | 213 | EditorGUI.BeginDisabledGroup(needsToResizeTexture || subTextures == null || 214 | subTextures.Length == 0); 215 | if (GUILayout.Button("Slice")) 216 | { 217 | PerformSlice(); 218 | } 219 | EditorGUI.EndDisabledGroup(); 220 | } 221 | EditorGUI.EndDisabledGroup(); 222 | } 223 | EditorGUI.EndDisabledGroup(); 224 | } 225 | 226 | private void PerformSlice() 227 | { 228 | if (importer == null) 229 | { 230 | return; 231 | } 232 | 233 | var textureHeight = selectedTexture.height; 234 | 235 | var needsUpdate = false; 236 | 237 | if (importer.spriteImportMode != SpriteImportMode.Multiple) 238 | { 239 | needsUpdate = true; 240 | importer.spriteImportMode = SpriteImportMode.Multiple; 241 | } 242 | 243 | var wantedSpriteSheet = (from subTexture in subTextures 244 | let actualY = textureHeight - (subTexture.y + subTexture.height) 245 | select new SpriteMetaData 246 | { 247 | alignment = (int)spriteAlignment, 248 | border = new Vector4(), 249 | name = subTexture.name, 250 | pivot = GetPivotValue(spriteAlignment, customOffset), 251 | rect = new Rect(subTexture.x, actualY, subTexture.width, subTexture.height) 252 | }).ToArray(); 253 | if (!needsUpdate && !importer.spritesheet.SequenceEqual(wantedSpriteSheet)) 254 | { 255 | needsUpdate = true; 256 | importer.spritesheet = wantedSpriteSheet; 257 | } 258 | 259 | if (needsUpdate) 260 | { 261 | EditorUtility.SetDirty(importer); 262 | 263 | try 264 | { 265 | AssetDatabase.StartAssetEditing(); 266 | AssetDatabase.ImportAsset(importer.assetPath); 267 | 268 | EditorUtility.DisplayDialog("Success!", "The sprite was sliced successfully.", "OK"); 269 | } 270 | catch (Exception exception) 271 | { 272 | Debug.LogException(exception); 273 | EditorUtility.DisplayDialog("Error", "There was an exception while trying to reimport the image." + 274 | "\nPlease check the console log for details.", "OK"); 275 | } 276 | finally 277 | { 278 | AssetDatabase.StopAssetEditing(); 279 | } 280 | } 281 | else 282 | { 283 | EditorUtility.DisplayDialog("Nope!", "The sprite is already sliced according to this Cocos2d-x Plist file.", "OK"); 284 | } 285 | } 286 | 287 | //SpriteEditorUtility 288 | public static Vector2 GetPivotValue(SpriteAlignment alignment, Vector2 customOffset) 289 | { 290 | switch (alignment) 291 | { 292 | case SpriteAlignment.Center: 293 | return new Vector2(0.5f, 0.5f); 294 | case SpriteAlignment.TopLeft: 295 | return new Vector2(0.0f, 1f); 296 | case SpriteAlignment.TopCenter: 297 | return new Vector2(0.5f, 1f); 298 | case SpriteAlignment.TopRight: 299 | return new Vector2(1f, 1f); 300 | case SpriteAlignment.LeftCenter: 301 | return new Vector2(0.0f, 0.5f); 302 | case SpriteAlignment.RightCenter: 303 | return new Vector2(1f, 0.5f); 304 | case SpriteAlignment.BottomLeft: 305 | return new Vector2(0.0f, 0.0f); 306 | case SpriteAlignment.BottomCenter: 307 | return new Vector2(0.5f, 0.0f); 308 | case SpriteAlignment.BottomRight: 309 | return new Vector2(1f, 0.0f); 310 | case SpriteAlignment.Custom: 311 | return customOffset; 312 | default: 313 | return Vector2.zero; 314 | } 315 | } 316 | 317 | private struct SubTexture 318 | { 319 | public int width; 320 | public int height; 321 | public int x; 322 | public int y; 323 | public string name; 324 | } 325 | 326 | private class PlistFinder 327 | { 328 | private Hashtable cachedMap = new Hashtable(); 329 | private int lastPosition = 0; 330 | private XmlNode rootNode; 331 | 332 | public PlistFinder(XmlNode rootNode) 333 | { 334 | this.rootNode = rootNode; 335 | } 336 | 337 | public XmlNode FindValueByKey(string key) 338 | { 339 | XmlNode rlt = cachedMap[key] as XmlNode; 340 | if (rlt != null) return rlt; 341 | 342 | XmlNodeList childs = rootNode.ChildNodes; 343 | for (int i = lastPosition; i < childs.Count; i++) 344 | { 345 | XmlNode child = childs[i]; 346 | if (child.Name.ToLower() == "key") 347 | { 348 | i++; 349 | cachedMap[child.InnerText] = childs[i]; 350 | if (child.InnerText == key) 351 | { 352 | rlt = childs[i]; 353 | break; 354 | } 355 | } 356 | } 357 | return rlt; 358 | } 359 | } 360 | } -------------------------------------------------------------------------------- /art/intro.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nekocode/Unity3D-TextureAtlasSlicer-Cocos2d-x/386cf6b63112129c3b2564348cb406d6fa92f5d1/art/intro.png -------------------------------------------------------------------------------- /art/menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nekocode/Unity3D-TextureAtlasSlicer-Cocos2d-x/386cf6b63112129c3b2564348cb406d6fa92f5d1/art/menu.png --------------------------------------------------------------------------------