Unity杂谈(一)图集制作与图集分割

1、图集制作

1.1、NGUI下的图集制作

首先将需要制作成图集的小图放到Unity工程下的某个目录下,再通过NGUI->Open->Atlas Maker。如下图

Unity杂谈(一)图集制作与图集分割_第1张图片

将会打开图集制作界面,如下图。在选择好需要制作成图集的小图后,点击Craete,将生成NGUI生成好的图集。

Unity杂谈(一)图集制作与图集分割_第2张图片

NGUI将生成三个文件,分别是png文件(图集);material文件(材质);prefab文件(实际用到的Atlas)。

 

1.2 UGUI制作图集

UGUI打包图集的设置在Project Seting >>Editor>>Sprite Packer的Mode。

Unity杂谈(一)图集制作与图集分割_第3张图片

其中,Disabled表示不启用图集打包,Enabled For Builds 表示仅在发布时启用。Always Enabled表示一直启用,方便我们开发人员在开发阶段就查看图集打包的情况。我们选择这种方式。

在开启图集打包后,我们选中需要打包成图集的小图(不能放在Resources目录下),将Packing Tag填入指定的值。如下

Unity杂谈(一)图集制作与图集分割_第4张图片

此时,UGUI将会把Packing Tag值相同的图片打包到同一个图集中,可在Window->Sprite Packer中查看。(如果未出现图集,点击Pack)。实际生成的图集在默认放在Libary/AtlasCache里面。

Unity杂谈(一)图集制作与图集分割_第5张图片

1.3 使用TexurePacker

TexturePacker官网链接

下载TexturePacker并安装后,打开TexturePacker,如下图

Unity杂谈(一)图集制作与图集分割_第6张图片

将需要合成图集的小图拖拽到左侧空白处,并在右侧的Data Format 选择 JSON Data(当然可以选择其他格式的文件,这边只是其中的一种方法)。

Unity杂谈(一)图集制作与图集分割_第7张图片

然后点击publish sprite sheet。生成所需要的图集。

在Unity中,可以通过json文件保存的信息进行图集的再次slice。我在网上找到一些代码,可以对图集进行分割。

TexturePacker.cs

/*
Copyright (c) 2013 Mitch Thompson
Extended by Harald Lurger (2013) (Process to Sprites)

Standard MIT License

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

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

public static class TexturePackerExtensions{
	public static Rect TPHashtableToRect(this Hashtable table){
		return new Rect((float)table["x"], (float)table["y"], (float)table["w"], (float)table["h"]);
	}
	
	public static Vector2 TPHashtableToVector2(this Hashtable table){
		if(table.ContainsKey("x") && table.ContainsKey("y")){
			return new Vector2((float)table["x"], (float)table["y"]);
		}
		else{
			return new Vector2((float)table["w"], (float)table["h"]);
		}
	}
	
	public static Vector2 TPVector3toVector2(this Vector3 vec){
		return new Vector2(vec.x, vec.y);
	}
	
	public static bool IsTexturePackerTable(this Hashtable table){
		if(table == null) return false;
		
		if(table.ContainsKey("meta")){
			Hashtable metaTable = (Hashtable)table["meta"];
			if(metaTable.ContainsKey("app")){
				return true;
//				if((string)metaTable["app"] == "http://www.texturepacker.com"){
//					return true;	
//				}
			}
		}
		
		return false;
	}
}

public class TexturePacker{

	public class PackedFrame{
		public string name;
		public Rect frame;
		public Rect spriteSourceSize;
		public Vector2 sourceSize;
		public bool rotated;
		public bool trimmed;
		Vector2 atlasSize;
		
		public PackedFrame(string name, Vector2 atlasSize, Hashtable table){
			this.name = name;
			this.atlasSize = atlasSize;
			
			frame = ((Hashtable)table["frame"]).TPHashtableToRect();
			spriteSourceSize = ((Hashtable)table["spriteSourceSize"]).TPHashtableToRect();
			sourceSize = ((Hashtable)table["sourceSize"]).TPHashtableToVector2();
			rotated = (bool)table["rotated"];
			trimmed = (bool)table["trimmed"];
		}
		
		public Mesh BuildBasicMesh(float scale, Color32 defaultColor){
			return BuildBasicMesh(scale, defaultColor, Quaternion.identity);
		}
		
		public Mesh BuildBasicMesh(float scale, Color32 defaultColor, Quaternion rotation){
			Mesh m = new Mesh();
			Vector3[] verts = new Vector3[4];
			Vector2[] uvs = new Vector2[4];
			Color32[] colors = new Color32[4];
		
			
			if(!rotated){
				verts[0] = new Vector3(frame.x,frame.y,0);
				verts[1] = new Vector3(frame.x,frame.y+frame.height,0);
				verts[2] = new Vector3(frame.x+frame.width,frame.y+frame.height,0);
				verts[3] = new Vector3(frame.x+frame.width,frame.y,0);
			}
			else{
				verts[0] = new Vector3(frame.x,frame.y,0);
				verts[1] = new Vector3(frame.x,frame.y+frame.width,0);
				verts[2] = new Vector3(frame.x+frame.height,frame.y+frame.width,0);
				verts[3] = new Vector3(frame.x+frame.height,frame.y,0);
			}
			
			

			
			uvs[0] = verts[0].TPVector3toVector2();
			uvs[1] = verts[1].TPVector3toVector2();
			uvs[2] = verts[2].TPVector3toVector2();
			uvs[3] = verts[3].TPVector3toVector2();
			
			for(int i = 0; i < uvs.Length; i++){
				uvs[i].x /= atlasSize.x;
				uvs[i].y /= atlasSize.y;
				uvs[i].y = 1.0f - uvs[i].y;
			}
			
			if(rotated){
				verts[3] = new Vector3(frame.x,frame.y,0);
				verts[0] = new Vector3(frame.x,frame.y+frame.height,0);
				verts[1] = new Vector3(frame.x+frame.width,frame.y+frame.height,0);
				verts[2] = new Vector3(frame.x+frame.width,frame.y,0);
			}
			
			
			//v-flip
			for(int i = 0; i < verts.Length; i++){
				verts[i].y = atlasSize.y - verts[i].y;
			}
			
			//original origin
			for(int i = 0; i < verts.Length; i++){
				verts[i].x -= frame.x - spriteSourceSize.x + (sourceSize.x/2.0f);
				verts[i].y -= (atlasSize.y - frame.y) - (sourceSize.y - spriteSourceSize.y) + (sourceSize.y/2.0f);
			}
			
			//scaler
			for(int i = 0; i < verts.Length; i++){
				verts[i] *= scale;
			}
			
			//rotator
			if(rotation != Quaternion.identity){
				for(int i = 0; i < verts.Length; i++){
					verts[i] = rotation * verts[i];
				}
			}
		
			for(int i = 0; i < colors.Length; i++){
				colors[i] = defaultColor;
			}
			
			
			m.vertices = verts;
			m.uv = uvs;
			m.colors32 = colors;
			m.triangles = new int[6]{0,3,1,1,3,2};
			
			m.RecalculateNormals();
			m.RecalculateBounds();
			m.name = name;
			
			return m;
		}

		public SpriteMetaData BuildBasicSprite(float scale, Color32 defaultColor){
			SpriteMetaData smd = new SpriteMetaData();
			Rect rect;

			if(!rotated){
				rect = this.frame;
			}
			else
			{
				rect = new Rect(frame.x,frame.y,frame.height,frame.width);
			}


			/* Look if frame is outside from texture */ 

			if( (frame.x + frame.width) > atlasSize.x || (frame.y + frame.height) > atlasSize.y ||
			    (frame.x < 0 || frame.y < 0)) 
			{
				Debug.Log(this.name + " is outside from texture! Sprite is ignored!");
				smd.name = "IGNORE_SPRITE";
				return smd;

			}
			//calculate Height 
		 	/* Example: Texture: 1000 Width x 500 height 
		 	 * Sprite.Recht(0,0,100,100) --> Sprite is on the bottom left
			 */

			rect.y = atlasSize.y - frame.y - rect.height;

			smd.rect = rect;
			smd.alignment =  1;
			smd.name = name;
			smd.pivot = this.frame.center;

			return smd;
		}
	}
	
	public class MetaData{
		public string image;
		public string format;
		public Vector2 size;
		public float scale;
		public string smartUpdate;
		
		public MetaData(Hashtable table){
			image = (string)table["image"];
			format = (string)table["format"];
			size = ((Hashtable)table["size"]).TPHashtableToVector2();
			scale = float.Parse(table["scale"].ToString());
			smartUpdate = (string)table["smartUpdate"];
		}
	}

	public static List ProcessToSprites(string text) {
		Hashtable table = text.hashtableFromJson();
		
		MetaData meta = new MetaData((Hashtable)table["meta"]);
		
		List frames = new List();
		Hashtable frameTable = (Hashtable)table["frames"];
		
		foreach(DictionaryEntry entry in frameTable){
			frames.Add(new PackedFrame((string)entry.Key, meta.size, (Hashtable)entry.Value));
		}

		SortFrames(frames);

		List sprites = new List();
		for(int i = 0; i < frames.Count; i++){
			SpriteMetaData smd = frames[i].BuildBasicSprite( 0.01f, new Color32(128,128,128,128));
			if(!smd.name.Equals("IGNORE_SPRITE"))
				sprites.Add(smd);
		}

		return sprites;

	}

	private static List SortFrames (List frames) {
		for (int i=frames.Count-1; i>0; i--) {
			for (int j=0; j frames = new List();
		Hashtable frameTable = (Hashtable)table["frames"];
		
		foreach(DictionaryEntry entry in frameTable){
			frames.Add(new PackedFrame((string)entry.Key, meta.size, (Hashtable)entry.Value));
		}
		
		List meshes = new List();
		for(int i = 0; i < frames.Count; i++){
			meshes.Add(frames[i].BuildBasicMesh(0.01f, new Color32(128,128,128,128), rotation));
		}
		
		return meshes.ToArray();
	}
	
	public static MetaData GetMetaData(string text){
		Hashtable table = text.hashtableFromJson();
		MetaData meta = new MetaData((Hashtable)table["meta"]);
		
		return meta;
	}
}

MiniJSON.cs

using System;
using System.Collections;
using System.Text;
using System.Collections.Generic;
using UnityEngine;

/* Based on the JSON parser from 
 * http://techblog.procurios.nl/k/618/news/view/14605/14863/How-do-I-write-my-own-parser-for-JSON.html
 * 
 * I simplified it so that it doesn't throw exceptions
 * and can be used in Unity iPhone with maximum code stripping.
 * 
 * Additional:
 * Modified by Mitch Thompson to output new-line formatting to make things more readable
 * Also modified to parse all numbers to float or single instead of double
 * Also modified to support float Infinity (defaults to Positive Infinity)
 */
/// 
/// This class encodes and decodes JSON strings.
/// Spec. details, see http://www.json.org/
/// 
/// JSON uses Arrays and Objects. These correspond here to the datatypes ArrayList and Hashtable.
/// All numbers are parsed to doubles.
/// 
public class MiniJSON
{
	private const int TOKEN_NONE = 0;
	private const int TOKEN_CURLY_OPEN = 1;
	private const int TOKEN_CURLY_CLOSE = 2;
	private const int TOKEN_SQUARED_OPEN = 3;
	private const int TOKEN_SQUARED_CLOSE = 4;
	private const int TOKEN_COLON = 5;
	private const int TOKEN_COMMA = 6;
	private const int TOKEN_STRING = 7;
	private const int TOKEN_NUMBER = 8;
	private const int TOKEN_TRUE = 9;
	private const int TOKEN_FALSE = 10;
	private const int TOKEN_NULL = 11;
	//added token for infinity
	private const int TOKEN_INFINITY = 12;
	private const int BUILDER_CAPACITY = 2000;

	/// 
	/// On decoding, this value holds the position at which the parse failed (-1 = no error).
	/// 
	protected static int lastErrorIndex = -1;
	protected static string lastDecode = "";


	/// 
	/// Parses the string json into a value
	/// 
	/// A JSON string.
	/// An ArrayList, a Hashtable, a double, a string, null, true, or false
	public static object jsonDecode( string json )
	{
		// save the string for debug information
		MiniJSON.lastDecode = json;

		if( json != null )
		{
			char[] charArray = json.ToCharArray();
			int index = 0;
			bool success = true;
			object value = MiniJSON.parseValue( charArray, ref index, ref success );

			if( success )
				MiniJSON.lastErrorIndex = -1;
			else
				MiniJSON.lastErrorIndex = index;

			return value;
		}
		else
		{
			return null;
		}
	}


	/// 
	/// Converts a Hashtable / ArrayList / Dictionary(string,string) object into a JSON string
	/// 
	/// A Hashtable / ArrayList
	/// A JSON encoded string, or null if object 'json' is not serializable
	public static string jsonEncode( object json )
	{
		indentLevel = 0;
		var builder = new StringBuilder( BUILDER_CAPACITY );
		var success = MiniJSON.serializeValue( json, builder );
		
		return ( success ? builder.ToString() : null );
	}


	/// 
	/// On decoding, this function returns the position at which the parse failed (-1 = no error).
	/// 
	/// 
	public static bool lastDecodeSuccessful()
	{
		return ( MiniJSON.lastErrorIndex == -1 );
	}


	/// 
	/// On decoding, this function returns the position at which the parse failed (-1 = no error).
	/// 
	/// 
	public static int getLastErrorIndex()
	{
		return MiniJSON.lastErrorIndex;
	}


	/// 
	/// If a decoding error occurred, this function returns a piece of the JSON string 
	/// at which the error took place. To ease debugging.
	/// 
	/// 
	public static string getLastErrorSnippet()
	{
		if( MiniJSON.lastErrorIndex == -1 )
		{
			return "";
		}
		else
		{
			int startIndex = MiniJSON.lastErrorIndex - 5;
			int endIndex = MiniJSON.lastErrorIndex + 15;
			if( startIndex < 0 )
				startIndex = 0;

			if( endIndex >= MiniJSON.lastDecode.Length )
				endIndex = MiniJSON.lastDecode.Length - 1;

			return MiniJSON.lastDecode.Substring( startIndex, endIndex - startIndex + 1 );
		}
	}

	
	#region Parsing
	
	protected static Hashtable parseObject( char[] json, ref int index )
	{
		Hashtable table = new Hashtable();
		int token;

		// {
		nextToken( json, ref index );

		bool done = false;
		while( !done )
		{
			token = lookAhead( json, index );
			if( token == MiniJSON.TOKEN_NONE )
			{
				return null;
			}
			else if( token == MiniJSON.TOKEN_COMMA )
			{
				nextToken( json, ref index );
			}
			else if( token == MiniJSON.TOKEN_CURLY_CLOSE )
			{
				nextToken( json, ref index );
				return table;
			}
			else
			{
				// name
				string name = parseString( json, ref index );
				if( name == null )
				{
					return null;
				}

				// :
				token = nextToken( json, ref index );
				if( token != MiniJSON.TOKEN_COLON )
					return null;

				// value
				bool success = true;
				object value = parseValue( json, ref index, ref success );
				if( !success )
					return null;

				table[name] = value;
			}
		}

		return table;
	}

	
	protected static ArrayList parseArray( char[] json, ref int index )
	{
		ArrayList array = new ArrayList();

		// [
		nextToken( json, ref index );

		bool done = false;
		while( !done )
		{
			int token = lookAhead( json, index );
			if( token == MiniJSON.TOKEN_NONE )
			{
				return null;
			}
			else if( token == MiniJSON.TOKEN_COMMA )
			{
				nextToken( json, ref index );
			}
			else if( token == MiniJSON.TOKEN_SQUARED_CLOSE )
			{
				nextToken( json, ref index );
				break;
			}
			else
			{
				bool success = true;
				object value = parseValue( json, ref index, ref success );
				if( !success )
					return null;

				array.Add( value );
			}
		}

		return array;
	}

	
	protected static object parseValue( char[] json, ref int index, ref bool success )
	{
		switch( lookAhead( json, index ) )
		{
			case MiniJSON.TOKEN_STRING:
				return parseString( json, ref index );
			case MiniJSON.TOKEN_NUMBER:
				return parseNumber( json, ref index );
			case MiniJSON.TOKEN_CURLY_OPEN:
				return parseObject( json, ref index );
			case MiniJSON.TOKEN_SQUARED_OPEN:
				return parseArray( json, ref index );
			case MiniJSON.TOKEN_TRUE:
				nextToken( json, ref index );
				return Boolean.Parse( "TRUE" );
			case MiniJSON.TOKEN_FALSE:
				nextToken( json, ref index );
				return Boolean.Parse( "FALSE" );
			case MiniJSON.TOKEN_NULL:
				nextToken( json, ref index );
				return null;
			case MiniJSON.TOKEN_INFINITY:
				nextToken( json, ref index );
				return float.PositiveInfinity;
			case MiniJSON.TOKEN_NONE:
				break;
		}

		success = false;
		return null;
	}

	
	protected static string parseString( char[] json, ref int index )
	{
		string s = "";
		char c;

		eatWhitespace( json, ref index );
		
		// "
		c = json[index++];

		bool complete = false;
		while( !complete )
		{
			if( index == json.Length )
				break;

			c = json[index++];
			if( c == '"' )
			{
				complete = true;
				break;
			}
			else if( c == '\\' )
			{
				if( index == json.Length )
					break;

				c = json[index++];
				if( c == '"' )
				{
					s += '"';
				}
				else if( c == '\\' )
				{
					s += '\\';
				}
				else if( c == '/' )
				{
					s += '/';
				}
				else if( c == 'b' )
				{
					s += '\b';
				}
				else if( c == 'f' )
				{
					s += '\f';
				}
				else if( c == 'n' )
				{
					s += '\n';
				}
				else if( c == 'r' )
				{
					s += '\r';
				}
				else if( c == 't' )
				{
					s += '\t';
				}
				else if( c == 'u' )
				{
					int remainingLength = json.Length - index;
					if( remainingLength >= 4 )
					{
						char[] unicodeCharArray = new char[4];
						Array.Copy( json, index, unicodeCharArray, 0, 4 );

						// Drop in the HTML markup for the unicode character
						s += "&#x" + new string( unicodeCharArray ) + ";";

						/*
uint codePoint = UInt32.Parse(new string(unicodeCharArray), NumberStyles.HexNumber);
// convert the integer codepoint to a unicode char and add to string
s += Char.ConvertFromUtf32((int)codePoint);
*/

						// skip 4 chars
						index += 4;
					}
					else
					{
						break;
					}

				}
			}
			else
			{
				s += c;
			}

		}

		if( !complete )
			return null;

		return s;
	}
	
	
	protected static float parseNumber( char[] json, ref int index )
	{
		eatWhitespace( json, ref index );

		int lastIndex = getLastIndexOfNumber( json, index );
		int charLength = ( lastIndex - index ) + 1;
		char[] numberCharArray = new char[charLength];

		Array.Copy( json, index, numberCharArray, 0, charLength );
		index = lastIndex + 1;
		return float.Parse( new string( numberCharArray ) ); // , CultureInfo.InvariantCulture);
	}
	
	
	protected static int getLastIndexOfNumber( char[] json, int index )
	{
		int lastIndex;
		for( lastIndex = index; lastIndex < json.Length; lastIndex++ )
			if( "0123456789+-.eE".IndexOf( json[lastIndex] ) == -1 )
			{
				break;
			}
		return lastIndex - 1;
	}
	
	
	protected static void eatWhitespace( char[] json, ref int index )
	{
		for( ; index < json.Length; index++ )
			if( " \t\n\r".IndexOf( json[index] ) == -1 )
			{
				break;
			}
	}
	
	
	protected static int lookAhead( char[] json, int index )
	{
		int saveIndex = index;
		return nextToken( json, ref saveIndex );
	}

	
	protected static int nextToken( char[] json, ref int index )
	{
		eatWhitespace( json, ref index );

		if( index == json.Length )
		{
			return MiniJSON.TOKEN_NONE;
		}
		
		char c = json[index];
		index++;
		switch( c )
		{
			case '{':
				return MiniJSON.TOKEN_CURLY_OPEN;
			case '}':
				return MiniJSON.TOKEN_CURLY_CLOSE;
			case '[':
				return MiniJSON.TOKEN_SQUARED_OPEN;
			case ']':
				return MiniJSON.TOKEN_SQUARED_CLOSE;
			case ',':
				return MiniJSON.TOKEN_COMMA;
			case '"':
				return MiniJSON.TOKEN_STRING;
			case '0':
			case '1':
			case '2':
			case '3':
			case '4': 
			case '5':
			case '6':
			case '7':
			case '8':
			case '9':
			case '-': 
				return MiniJSON.TOKEN_NUMBER;
			case ':':
				return MiniJSON.TOKEN_COLON;
		}
		index--;

		int remainingLength = json.Length - index;
		
		// Infinity
		if( remainingLength >= 8 )
		{
			if( json[index] == 'I' &&
				json[index + 1] == 'n' &&
				json[index + 2] == 'f' &&
				json[index + 3] == 'i' &&
				json[index + 4] == 'n' &&
				json[index + 5] == 'i' &&
				json[index + 6] == 't' &&
				json[index + 7] == 'y' )
			{
				index += 8;
				return MiniJSON.TOKEN_INFINITY;
			}
		}
		
		// false
		if( remainingLength >= 5 )
		{
			if( json[index] == 'f' &&
				json[index + 1] == 'a' &&
				json[index + 2] == 'l' &&
				json[index + 3] == 's' &&
				json[index + 4] == 'e' )
			{
				index += 5;
				return MiniJSON.TOKEN_FALSE;
			}
		}

		// true
		if( remainingLength >= 4 )
		{
			if( json[index] == 't' &&
				json[index + 1] == 'r' &&
				json[index + 2] == 'u' &&
				json[index + 3] == 'e' )
			{
				index += 4;
				return MiniJSON.TOKEN_TRUE;
			}
		}

		// null
		if( remainingLength >= 4 )
		{
			if( json[index] == 'n' &&
				json[index + 1] == 'u' &&
				json[index + 2] == 'l' &&
				json[index + 3] == 'l' )
			{
				index += 4;
				return MiniJSON.TOKEN_NULL;
			}
		}

		return MiniJSON.TOKEN_NONE;
	}

	#endregion
	
	
	#region Serialization
	
	protected static bool serializeObjectOrArray( object objectOrArray, StringBuilder builder )
	{
		if( objectOrArray is Hashtable )
		{
			return serializeObject( (Hashtable)objectOrArray, builder );
		}
		else if( objectOrArray is ArrayList )
			{
				return serializeArray( (ArrayList)objectOrArray, builder );
			}
			else
			{
				return false;
			}
	}

	protected static int indentLevel = 0;
	protected static bool serializeObject( Hashtable anObject, StringBuilder builder )
	{
		
		indentLevel++;
		string indentString = "";
		for(int i = 0; i < indentLevel; i++)
			indentString += "\t";
		
		builder.Append( "{\n" + indentString );
		
		
		
		IDictionaryEnumerator e = anObject.GetEnumerator();
		bool first = true;
		while( e.MoveNext() )
		{
			string key = e.Key.ToString();
			object value = e.Value;

			if( !first )
			{
				builder.Append( ", \n" + indentString );
			}

			serializeString( key, builder );
			builder.Append( ":" );
			if( !serializeValue( value, builder ) )
			{
				return false;
			}

			first = false;
		}
		
		
		indentString = "";
		for(int i = 0; i < indentLevel-1; i++)
			indentString += "\t";
		
		builder.Append( "\n" + indentString + "}");
		
		indentLevel--;
		return true;
	}
	
	
	protected static bool serializeDictionary( Dictionary dict, StringBuilder builder )
	{
		builder.Append( "{" );
		
		bool first = true;
		foreach( var kv in dict )
		{
			if( !first )
				builder.Append( ", " );
			
			serializeString( kv.Key, builder );
			builder.Append( ":" );
			serializeString( kv.Value, builder );

			first = false;
		}

		builder.Append( "}" );
		return true;
	}
	
	
	protected static bool serializeArray( ArrayList anArray, StringBuilder builder )
	{
		indentLevel++;
		string indentString = "";
		for(int i = 0; i < indentLevel; i++)
			indentString += "\t";
		
		builder.Append( "[\n" + indentString );
//		builder.Append( "[" );
		
		
		
		

		bool first = true;
		for( int i = 0; i < anArray.Count; i++ )
		{
			object value = anArray[i];

			if( !first )
			{
				builder.Append( ", \n" + indentString );
			}

			if( !serializeValue( value, builder ) )
			{
				return false;
			}

			first = false;
		}

		
		
		indentString = "";
		for(int i = 0; i < indentLevel-1; i++)
			indentString += "\t";
		
		builder.Append( "\n" + indentString + "]");
		
		indentLevel--;
		
		return true;
	}

	
	protected static bool serializeValue( object value, StringBuilder builder )
	{
		// Type t = value.GetType();
		// Debug.Log("type: " + t.ToString() + " isArray: " + t.IsArray);

		if( value == null )
		{
			builder.Append( "null" );
		}
		else if( value.GetType().IsArray )
		{
			serializeArray( new ArrayList( (ICollection)value ), builder );
		}
		else if( value is string )
		{
			serializeString( (string)value, builder );
		}
		else if( value is Char )
		{
			serializeString( Convert.ToString( (char)value ), builder );
		}
		else if( value is Hashtable )
		{
			serializeObject( (Hashtable)value, builder );
		}
		else if( value is Dictionary )
		{
			serializeDictionary( (Dictionary)value, builder );
		}
		else if( value is ArrayList )
		{
			serializeArray( (ArrayList)value, builder );
		}
		else if( ( value is Boolean ) && ( (Boolean)value == true ) )
		{
			builder.Append( "true" );
		}
		else if( ( value is Boolean ) && ( (Boolean)value == false ) )
		{
			builder.Append( "false" );
		}
		else if( value.GetType().IsPrimitive )
		{
			serializeNumber( Convert.ToSingle( value ), builder );
		}
		else
		{
			return false;
		}

		return true;
	}

	
	protected static void serializeString( string aString, StringBuilder builder )
	{
		builder.Append( "\"" );

		char[] charArray = aString.ToCharArray();
		for( int i = 0; i < charArray.Length; i++ )
		{
			char c = charArray[i];
			if( c == '"' )
			{
				builder.Append( "\\\"" );
			}
			else if( c == '\\' )
			{
				builder.Append( "\\\\" );
			}
			else if( c == '\b' )
			{
				builder.Append( "\\b" );
			}
			else if( c == '\f' )
			{
				builder.Append( "\\f" );
			}
			else if( c == '\n' )
			{
				builder.Append( "\\n" );
			}
			else if( c == '\r' )
			{
				builder.Append( "\\r" );
			}
			else if( c == '\t' )
			{
				builder.Append( "\\t" );
			}
			else
			{
				int codepoint = Convert.ToInt32( c );
				if( ( codepoint >= 32 ) && ( codepoint <= 126 ) )
				{
					builder.Append( c );
				}
				else
				{
					builder.Append( "\\u" + Convert.ToString( codepoint, 16 ).PadLeft( 4, '0' ) );
				}
			}
		}

		builder.Append( "\"" );
	}

	
	protected static void serializeNumber( float number, StringBuilder builder )
	{
		builder.Append( Convert.ToString( number ) ); // , CultureInfo.InvariantCulture));
	}
	
	#endregion
	
}



#region Extension methods

public static class MiniJsonExtensions
{
	public static string toJson( this Hashtable obj )
	{
		return MiniJSON.jsonEncode( obj );
	}
		
	public static string toJson( this Dictionary obj )
	{
		return MiniJSON.jsonEncode( obj );
	}
	
	
	public static ArrayList arrayListFromJson( this string json )
	{
		return MiniJSON.jsonDecode( json ) as ArrayList;
	}


	public static Hashtable hashtableFromJson( this string json )
	{
		return MiniJSON.jsonDecode( json ) as Hashtable;
	}
}

#endregion

TexturePackerImport.cs

/*
Copyright (c) 2013 Mitch Thompson
Extended by Harald Lurger (2013) (Process to Sprites)

Standard MIT License

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

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


public static class TexturePackerImport{

	[MenuItem("Assets/TexturePacker/Process to Sprites")]
	static void ProcessToSprite(){
		TextAsset txt = (TextAsset)Selection.activeObject;

		string rootPath = Path.GetDirectoryName(AssetDatabase.GetAssetPath(txt));
		TexturePacker.MetaData meta = TexturePacker.GetMetaData(txt.text);
		
		List sprites = TexturePacker.ProcessToSprites(txt.text);

		string path = rootPath + "/" + meta.image;
		TextureImporter texImp = AssetImporter.GetAtPath(path) as TextureImporter;
		texImp.spritesheet = sprites.ToArray();
		texImp.textureType = TextureImporterType.Sprite;
		texImp.spriteImportMode =SpriteImportMode.Multiple;

		AssetDatabase.ImportAsset(path, ImportAssetOptions.ForceUpdate );
	
	}

	[MenuItem("Assets/TexturePacker/Process to Meshes")]
	static Mesh[] ProcessToMeshes(){
		TextAsset txt = (TextAsset)Selection.activeObject;
		
		Quaternion rotation = Quaternion.identity;
		string pref = EditorPrefs.GetString("TexturePackerImporterFacing", "back");
		
		switch(pref){
		case "back":
			rotation = Quaternion.identity;
			break;
		case "forward":
			rotation = Quaternion.LookRotation(Vector3.back);
			break;
		case "up":
			rotation = Quaternion.LookRotation(Vector3.down, Vector3.forward);
			break;
		case "down":
			rotation = Quaternion.LookRotation(Vector3.up, Vector3.back);
			break;
		case "right":
			rotation = Quaternion.LookRotation(Vector3.left);
			break;
		case "left":
			rotation = Quaternion.LookRotation(Vector3.right);
			break;
		}
		
		
		Mesh[] meshes = TexturePacker.ProcessToMeshes(txt.text, rotation);
		
		string rootPath = Path.GetDirectoryName(AssetDatabase.GetAssetPath(txt));
		
		Directory.CreateDirectory(Application.dataPath + "/" + rootPath.Substring(7, rootPath.Length-7) + "/Meshes");
		
		Mesh[] returnMeshes = new Mesh[meshes.Length];
		
		int i = 0;
		foreach(Mesh m in meshes){
			string assetPath = rootPath + "/Meshes/" + Path.GetFileNameWithoutExtension(m.name) + ".asset";
			Mesh existingMesh = (Mesh)AssetDatabase.LoadAssetAtPath(assetPath, typeof(Mesh));
			if(existingMesh == null){
				AssetDatabase.CreateAsset(m, assetPath);
				existingMesh = (Mesh)AssetDatabase.LoadAssetAtPath(assetPath, typeof(Mesh));
			}
			else{
				existingMesh.triangles = new int[0];
				existingMesh.colors32 = new Color32[0];
				existingMesh.uv = new Vector2[0];
				existingMesh.vertices = m.vertices;
				existingMesh.uv = m.uv;
				existingMesh.colors32 = m.colors32;
				existingMesh.triangles = m.triangles;
				existingMesh.RecalculateNormals();
				existingMesh.RecalculateBounds();
				EditorUtility.SetDirty(existingMesh);
				Mesh.DestroyImmediate(m);
			}
			
			returnMeshes[i] = existingMesh;
			i++;
		}	
		
		return returnMeshes;
	}
	
	[MenuItem("Assets/TexturePacker/Process to Prefabs")]
	static void ProcessToPrefabs(){
		Mesh[] meshes = ProcessToMeshes();
		
		
		TextAsset txt = (TextAsset)Selection.activeObject;
		string rootPath = Path.GetDirectoryName(AssetDatabase.GetAssetPath(txt));
		
		string prefabPath = rootPath.Substring(7, rootPath.Length-7) + "/Prefabs";
		Directory.CreateDirectory(Application.dataPath + "/" + prefabPath);
		
		prefabPath = "Assets/" + prefabPath;
		
		
		//make material
		TexturePacker.MetaData meta = TexturePacker.GetMetaData(txt.text);
		
		string matPath = rootPath + "/" + (Path.GetFileNameWithoutExtension(meta.image) + ".mat");
		string texturePath = rootPath + "/" + meta.image;
		Material mat = (Material)AssetDatabase.LoadAssetAtPath(matPath, typeof(Material));
		if(mat == null){
			mat = new Material(Shader.Find("Sprites/Transparent Unlit"));
			Texture2D tex = (Texture2D)AssetDatabase.LoadAssetAtPath(texturePath, typeof(Texture2D));
			if(tex == null){
				EditorUtility.DisplayDialog("Error!", "Texture " + meta.image + " not found!", "Ok");	
			}
			mat.mainTexture = tex;
			AssetDatabase.CreateAsset(mat, matPath);
		}
		
		
		AssetDatabase.Refresh();
		
		for(int i = 0; i < meshes.Length; i++){
			string prefabFilePath = prefabPath + "/" + meshes[i].name + ".prefab";
			
			bool createdNewPrefab = false;
			Object prefab = AssetDatabase.LoadAssetAtPath(prefabFilePath, typeof(Object));
			
			if(prefab == null){
				prefab = PrefabUtility.CreateEmptyPrefab(prefabFilePath);
				createdNewPrefab = true;
			}
			
			if(createdNewPrefab){
				GameObject go = new GameObject(meshes[i].name, typeof(MeshRenderer), typeof(MeshFilter));
				go.GetComponent().sharedMesh = meshes[i];
				go.GetComponent().sharedMaterial = mat;
				
				PrefabUtility.ReplacePrefab(go, prefab, ReplacePrefabOptions.ConnectToPrefab);
				
				GameObject.DestroyImmediate(go);
			}
			else{
				GameObject pgo = (GameObject)prefab;
				pgo.GetComponent().sharedMaterial = mat;
				pgo.GetComponent().sharedMesh = meshes[i];
				EditorUtility.SetDirty(pgo);
			}
		}
	}
	
	//Validators
	[MenuItem("Assets/TexturePacker/Process to Prefabs", true)]
	[MenuItem("Assets/TexturePacker/Process to Meshes", true)]
	[MenuItem("Assets/TexturePacker/Process to Sprites", true)]
	static bool ValidateProcessTexturePacker(){
		Object o = Selection.activeObject;
		
		if(o == null)
			return false;
		
		if(o.GetType() == typeof(TextAsset)){
			return (((TextAsset)o).text.hashtableFromJson()).IsTexturePackerTable();
		}
		
		return false;

	}
	

	
	
	//Attach 90 degree "Shadow" meshes
	[MenuItem("Assets/TexturePacker/Attach Shadow Mesh")]
	static void AttachShadowMesh(){
		List meshes = new List();
		foreach(Object o in Selection.objects){
			if(o is Mesh) meshes.Add(o as Mesh);	
		}
		
		foreach(Mesh m in meshes){
			Vector3[] verts = new Vector3[m.vertexCount*2];
			Vector2[] uvs = new Vector2[m.vertexCount*2];
			Color32[] colors = new Color32[m.vertexCount*2];
			int[] triangles = new int[m.triangles.Length * 2];
			
			System.Array.Copy(m.vertices, 0, verts, m.vertexCount, m.vertexCount);
			System.Array.Copy(m.uv, 0, uvs, m.vertexCount, m.vertexCount);
			System.Array.Copy(m.colors32, 0, colors, m.vertexCount, m.vertexCount);
			System.Array.Copy(m.triangles, 0, triangles, m.triangles.Length, m.triangles.Length);
			
			for(int i = 0; i < m.vertexCount; i++){
				verts[i].x = verts[i+m.vertexCount].x;
				verts[i].y = verts[i+m.vertexCount].z;
				verts[i].z = verts[i+m.vertexCount].y;
				
				uvs[i] = uvs[i+m.vertexCount];
				colors[i] = new Color32(0,0,0,64);
				
					
			}
			
			for(int i = 0; i < m.triangles.Length; i++){
				triangles[i] = triangles[i + m.triangles.Length];
				triangles[i + m.triangles.Length] += m.vertexCount;
				
			}
						
			m.vertices = verts;
			m.uv = uvs;
			m.colors32 = colors;
			m.triangles = triangles;
			
			m.RecalculateNormals();
			m.RecalculateBounds();
			EditorUtility.SetDirty(m);
		}
	}
	
	
	//Validators
	[MenuItem("Assets/TexturePacker/Attach Shadow Mesh", true)]
	static bool ValidateAttachShadowMesh(){
		Object[] objs = Selection.objects;
		foreach(Object o in objs){
			if(!(o is Mesh)){
				return false;
			}
		}
		
		return true;
	}
	
	
	
	//Options
	[MenuItem("Assets/TexturePacker/Facing/Back")]
	static void SetFacingBack(){	EditorPrefs.SetString("TexturePackerImporterFacing", "back"); }
	
	[MenuItem("Assets/TexturePacker/Facing/Forward")]
	static void SetFacingForward(){	EditorPrefs.SetString("TexturePackerImporterFacing", "forward"); }
	
	[MenuItem("Assets/TexturePacker/Facing/Up")]
	static void SetFacingUp(){	EditorPrefs.SetString("TexturePackerImporterFacing", "up"); }
	
	[MenuItem("Assets/TexturePacker/Facing/Down")]
	static void SetFacingDown(){	EditorPrefs.SetString("TexturePackerImporterFacing", "down"); }
	
	[MenuItem("Assets/TexturePacker/Facing/Right")]
	static void SetFacingRight(){	EditorPrefs.SetString("TexturePackerImporterFacing", "right"); }
	
	[MenuItem("Assets/TexturePacker/Facing/Left")]
	static void SetFacingLeft(){	EditorPrefs.SetString("TexturePackerImporterFacing", "left"); }
}

MeshTweaker.cs

/*
	Mesh Tools - Mesh Tweaker
    Copyright (C) 2013 Mitch Thompson

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see .
*/

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

public class MeshTweaker{
	
	
	
	
	
	[MenuItem("Assets/MeshTweaker/Rotate/X45")]
	static void RotateX45(){BatchRotate(45,0,0);}
	[MenuItem("Assets/MeshTweaker/Rotate/X90")]
	static void RotateX90(){BatchRotate(90,0,0);}
	
	[MenuItem("Assets/MeshTweaker/Rotate/Y45")]
	static void RotateY45(){BatchRotate(0,45,0);}
	[MenuItem("Assets/MeshTweaker/Rotate/Y90")]
	static void RotateY90(){BatchRotate(0,90,0);}
	
	[MenuItem("Assets/MeshTweaker/Rotate/Z45")]
	static void RotateZ45(){BatchRotate(0,0,45);}
	[MenuItem("Assets/MeshTweaker/Rotate/Z90")]
	static void RotateZ90(){BatchRotate(0,0,90);}
	
	static void BatchRotate(float x, float y, float z){
		Quaternion quat = Quaternion.Euler(x,y,z);
		foreach(Object o in Selection.objects){
			if(o is Mesh){
				RotateMesh(o as Mesh, quat);
			}
		}	
	}
	
	
	static void RotateMesh(Mesh mesh, Quaternion quat){
		Vector3[] verts = mesh.vertices;
		for(int i = 0; i < verts.Length; i++){
			verts[i] = quat * verts[i];	
		}
		
		mesh.vertices = verts;
		
		mesh.RecalculateNormals();
		
		EditorUtility.SetDirty(mesh);
	}
	
	
	[MenuItem("Assets/MeshTweaker/Align/Center")]
	static void BatchCenter(){
		foreach(Object o in Selection.objects){
			if(o is Mesh){
				CenterMesh(o as Mesh);
			}
		}
	}
	
	static void CenterMesh(Mesh mesh){
		Vector3[] verts = mesh.vertices;
		
		Vector3 min = new Vector3(float.MaxValue, float.MaxValue, float.MaxValue);
		Vector3 max = new Vector3(float.MinValue, float.MinValue, float.MinValue);
		
		foreach(Vector3 v in verts){
			if(v.x < min.x) min.x = v.x;
			else if(v.x > max.x) max.x = v.x;
			
			if(v.y < min.y) min.y = v.y;
			else if(v.y > max.y) max.y = v.y;
			
			if(v.z < min.z) min.z = v.z;
			else if(v.z > max.z) max.z = v.z;
		}
		
		Vector3 average = (min + max) * 0.5f;
		
		for(int i = 0; i < verts.Length; i++){
			verts[i] = verts[i] + ( Vector3.zero - average );
		}
		
		mesh.vertices = verts;
		EditorUtility.SetDirty(mesh);
	}
}

TexturePivotParser.cs

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

public static class TexturePivotParser {

	[MenuItem("Assets/PivotParser/Process for Pivot")]
	static void ProcessForPivot () {
		TextAsset txt = (TextAsset)Selection.activeObject;

		string rootPath = Path.GetDirectoryName(AssetDatabase.GetAssetPath(txt));
		string textureName = GetTextureName(txt.text);
		string texturePath = rootPath + "/" + textureName;

		TextureImporter textureImporter = AssetImporter.GetAtPath(texturePath) as TextureImporter;

		if (textureImporter.spriteImportMode.Equals(SpriteImportMode.Multiple)) {
			List sprites = new List(textureImporter.spritesheet);
			Dictionary pivotTable = GetPivotsDict(txt.text);

			for (int i=0; i GetPivotsDict (string text) {
		Dictionary dict = new Dictionary();
		Hashtable table = text.hashtableFromJson();
		Hashtable pivotsTable = (Hashtable) table[("pivots")];

		foreach (string frameName in pivotsTable.Keys) {
			string pivotName = (string) pivotsTable[frameName];

			SpriteAlignment pivot = SpriteAlignment.Center;
	
			if (pivotName.Equals("top left")) {
				pivot = SpriteAlignment.TopLeft;
			}
			else if (pivotName.Equals("top")) {
				pivot = SpriteAlignment.TopCenter;
			}
			else if (pivotName.Equals("top right")) {
				pivot = SpriteAlignment.TopRight;
			}
			else if (pivotName.Equals("right")) {
				pivot = SpriteAlignment.RightCenter;
			}
			else if (pivotName.Equals("bottom right")) {
				pivot = SpriteAlignment.BottomRight;
			}
			else if (pivotName.Equals("bottom")) {
				pivot = SpriteAlignment.BottomCenter;
			}
			else if (pivotName.Equals("bottom left")) {
				pivot = SpriteAlignment.BottomLeft;
			}
			else if (pivotName.Equals("left")) {
				pivot = SpriteAlignment.LeftCenter;
			}
			else if (pivotName.Equals("center")) {
				pivot = SpriteAlignment.Center;
			}
			else {
				Debug.LogError("Texture Pivot Parser: Wrong pivot name!");
			}
			dict.Add(frameName, pivot);
		}
		return dict;
	}

	private static string GetTextureName (string text) {
		Hashtable table = text.hashtableFromJson();
		Hashtable metaTable = (Hashtable) table["meta"];
		return (string) metaTable["image"];
	}
}

2、图集拆分成小图

2.1 plist分割

有时候我们需要从cocos那边的图集拿来用,就需要把图集重新拆分,以便后续筛选使用。

在网上查找了一下,发现是基于python2.6的,我用的是python3,我便改动了一下,方便在python3的基础上使用。

首先安装python3,然后在命令行执行pip install Pillow(这一步是安装Pillow),将unpack_plist.py放到相对于的文件夹下

unpack_plist.py

# -*- coding: utf-8 -*-
import os,sys
from xml.etree import ElementTree
from PIL import Image

def tree_to_dict(tree):
    d = {}
    for index, item in enumerate(tree):
        if item.tag == 'key':
            if tree[index+1].tag == 'string':
                d[item.text] = tree[index + 1].text
            elif tree[index + 1].tag == 'true':
                d[item.text] = True
            elif tree[index + 1].tag == 'false':
                d[item.text] = False
            elif tree[index+1].tag == 'dict':
                d[item.text] = tree_to_dict(tree[index+1])
    return d 
    
def gen_png_from_plist(plist_filename, png_filename):
    file_path = plist_filename.replace('.plist', '')
    big_image = Image.open(png_filename)
    root = ElementTree.fromstring(open(plist_filename, 'r').read())
    plist_dict = tree_to_dict(root[0])
    to_list = lambda x: x.replace('{','').replace('}','').split(',')
    for k,v in plist_dict['frames'].items():
        if 'textureRect' in v:
            rectlist = to_list(v['textureRect'])
        elif 'frame' in v:
            rectlist = to_list(v['frame'])
        if 'rotated' in v:
            width = int( rectlist[3] if v['rotated'] else rectlist[2] )
            height = int( rectlist[2] if v['rotated'] else rectlist[3] )        
        else:
            width = int( rectlist[2] )
            height = int( rectlist[3] )
        box=( 
            int(rectlist[0]),
            int(rectlist[1]),
            int(rectlist[0]) + width,
            int(rectlist[1]) + height,
            )
        #print box
        #print v
        if 'spriteSize' in v:
            spriteSize = v['spriteSize']
        elif 'sourceSize' in v:
            spriteSize = v['sourceSize']
            
        sizelist = [ int(float(x)) for x in to_list(spriteSize)]
        #print sizelist
        rect_on_big = big_image.crop(box)

        if 'textureRotated' in v and v['textureRotated']:
            rect_on_big = rect_on_big.rotate(90)
        elif 'rotated' in v and v['rotated']:
            rect_on_big = rect_on_big.rotate(90)
        
        #if (('textureRotated' in v) and v['textureRotated']) or (('rotated' in v) and v['rotated'])):
            #rect_on_big = rect_on_big.rotate(90)

        result_image = Image.new('RGBA', sizelist, (0,0,0,0))
        
        if 'textureRotated' in v and v['textureRotated']:
            result_box=(
                ( sizelist[0] - height )/2,
                ( sizelist[1] - width )/2,
                ( sizelist[0] + height )/2,
                ( sizelist[1] + width )/2
                )
        elif 'rotated' in v and v['rotated']:
            result_box=(
                int(( sizelist[0] - height )/2),
                int(( sizelist[1] - width )/2),
                int(( sizelist[0] + height )/2),
                int(( sizelist[1] + width )/2)
                )
        else:
            result_box=(
                int(( sizelist[0] - width )/2),
                int(( sizelist[1] - height )/2),
                int(( sizelist[0] + width )/2),
                int(( sizelist[1] + height )/2)
                )
        print(result_box)
        result_image.paste(rect_on_big, result_box, mask=0)

        if not os.path.isdir(file_path):
            os.mkdir(file_path)
        k = k.replace('/', '_')
        outfile = (file_path+'/' + k).replace('gift_', '')
        #print k
        if outfile.find('.png') == -1:
            outfile = outfile + '.png'
        print(outfile, "generated")
        result_image.save(outfile)

if __name__ == '__main__':
    filename = sys.argv[1]
    plist_filename = filename + '.plist'
    png_filename = filename + '.png'
    if (os.path.exists(plist_filename) and os.path.exists(png_filename)):
        gen_png_from_plist( plist_filename, png_filename )
    else:
        print("make sure you have both plist and png files in the same directory")

最后执行unpack_plist 图集名称 便可以将图集重新分割为一个个小图。

 

2.2 Unity下将图集重新分割为小图

待填坑。。。

你可能感兴趣的:(Unity,Unity-杂谈)