[Unity3D] 5.0 图集合并扩展工具,用于解决UGUI与AssetBundle打包造成资源包过大的问题
[Unity3D] 5.0 图集合并扩展工具,用于解决UGUI与AssetBundle打包造成资源包过大的问题
http://www.cppblog.com/shly/archive/2015/12/09/212443.html
[Unity3D] 5.0 图集合并扩展工具,用于解决UGUI与AssetBundle打包造成资源包过大的问题
而当在使用AssetBundle的时候情况有些不同,会造成每个AB都会引入完整的Sprite图集,显然就增加了AB的大小,重复资源。
为了解决这个问题我们可以手动合并图集,那么方案也有多种,这里我们可以编辑器自带的API来实现一个小的图集合并工具。
private static bool CombineSpritesHelper(string path, string dpath, string name, int padding)
{
string[] paths = AssetDatabase.FindAssets("t:sprite", new string[] { path });
List<Sprite> spriteList = new List<Sprite>();
List<Texture2D> texList = new List<Texture2D>();
foreach (var o in paths)
{
Sprite s = AssetDatabase.LoadAssetAtPath(AssetDatabase.GUIDToAssetPath(o), typeof(Sprite)) as Sprite;
if (null != s)
{
spriteList.Add(s);
texList.Add(s.texture);
}
}
if (texList.Count > 0)
{
Texture2D tex = new Texture2D(1024, 1024, TextureFormat.ARGB32, true);
Rect[] uvs = UITexturePacker.PackTextures(tex, texList.ToArray(), 4, 4, padding, 1024);
if (null == uvs)
{
EditorUtility.DisplayDialog(path, "图集超过1024,需要分组成多张图集", "点击退出");
Object.DestroyImmediate(tex);
tex = null;
return false;
}
else
{
List<SpriteMetaData> metaList = new List<SpriteMetaData>();
for (int i = 0; i < uvs.Length; ++i)
{
SpriteMetaData data = new SpriteMetaData();
data.alignment = (int)SpriteAlignment.Center;
data.border = spriteList[i].border;
data.name = spriteList[i].name;
data.pivot = spriteList[i].pivot;
data.rect = new Rect(uvs[i].x * tex.width, uvs[i].y * tex.height, uvs[i].width * tex.width, uvs[i].height * tex.height);
metaList.Add(data);
}
//string dpath = path.Substring(0, path.Length - obj.name.Length) + "SpriteSet";
if (!System.IO.Directory.Exists(dpath))
{
System.IO.Directory.CreateDirectory(dpath);
}
string file = dpath + "/" + name + ".png";
if (System.IO.File.Exists(file))
{
System.IO.File.Delete(file);
}
System.IO.File.WriteAllBytes(file, tex.EncodeToPNG());
AssetDatabase.ImportAsset(file, ImportAssetOptions.ForceUpdate);
TextureImporter importer = AssetImporter.GetAtPath(file) as TextureImporter;
importer.spritesheet = metaList.ToArray();
importer.spriteImportMode = SpriteImportMode.Multiple;
importer.textureType = TextureImporterType.Sprite;
importer.textureFormat = TextureImporterFormat.ARGB32;
importer.mipmapEnabled = true;
importer.mipmapFilter = TextureImporterMipFilter.BoxFilter;
importer.assetBundleName = "ui_image/" + name.ToLower();
AssetDatabase.ImportAsset(file);
AssetDatabase.Refresh();
}
}
return true;
}
[MenuItem("Tool/Combine Sprites")]
[MenuItem("Assets/Tool/Combine Sprites")]
public static void CombineSprites()
{
EditorUtility.DisplayProgressBar("Combine Sprites", "Initializing
", 0);
try
{
Object obj = Selection.activeObject;
string path = AssetDatabase.GetAssetPath(obj.GetInstanceID());
string dpath = path.Substring(0, path.Length - obj.name.Length) + "SpriteSet";
if (System.IO.Directory.Exists(path))
{
string[] directories = System.IO.Directory.GetDirectories(path);
int count = 0;
if (directories.Length > 0)
{
foreach (var directory in directories)
{
count++;
EditorUtility.DisplayProgressBar("Combine Sprites", string.Format("combing
{0}", count), (float)(count) / (directories.Length));
if (!CombineSpritesHelper(directory, dpath, string.Concat(obj.name, "_", count.ToString()), 1))
{
break;
}
}
}
else
{
EditorUtility.DisplayProgressBar("Combine Sprites", "combing
0", 1);
CombineSpritesHelper(path, dpath, obj.name, 1);
}
}
}
catch (System.Exception e)
{
Debug.LogError(e);
}
EditorUtility.ClearProgressBar();
}使用方法很简单,可以修改源码中的路径,可以将多个Single Sprite合成一个Multi Sprite。然后在供,UGUI使用,注意最好保存好合并前的源文件,不然可能造成新增图片打图集后对应不上从而造成引用丢失。
补充UITextPacker.cs :引用自NGUI
Based on the Public Domain MaxRectsBinPack.cpp source by Jukka Jylänki
https://github.com/juj/RectangleBinPack/
Ported to C# by Sven Magnus
This version is also public domain - do whatever you want with it.
*/
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public class UITexturePacker
{
public int binWidth = 0;
public int binHeight = 0;
public bool allowRotations;
public List<Rect> usedRectangles = new List<Rect>();
public List<Rect> freeRectangles = new List<Rect>();
public enum FreeRectChoiceHeuristic
{
RectBestShortSideFit, ///< -BSSF: Positions the rectangle against the short side of a free rectangle into which it fits the best.
RectBestLongSideFit, ///< -BLSF: Positions the rectangle against the long side of a free rectangle into which it fits the best.
RectBestAreaFit, ///< -BAF: Positions the rectangle into the smallest free rect into which it fits.
RectBottomLeftRule, ///< -BL: Does the Tetris placement.
RectContactPointRule ///< -CP: Choosest the placement where the rectangle touches other rects as much as possible.
};
public UITexturePacker (int width, int height, bool rotations)
{
Init(width, height, rotations);
}
public void Init (int width, int height, bool rotations)
{
binWidth = width;
binHeight = height;
allowRotations = rotations;
Rect n = new Rect();
n.x = 0;
n.y = 0;
n.width = width;
n.height = height;
usedRectangles.Clear();
freeRectangles.Clear();
freeRectangles.Add(n);
}
private struct Storage
{
public Rect rect;
public bool paddingX;
public bool paddingY;
}
public static Rect[] PackTextures (Texture2D texture, Texture2D[] textures, int width, int height, int padding, int maxSize)
{
if (width > maxSize || height > maxSize) return null;
if (width > maxSize || height > maxSize) { int temp = width; width = height; height = temp; }
// Force square by sizing up
/*
if (NGUISettings.forceSquareAtlas)
{
if (width > height)
height = width;
else if (height > width)
width = height;
}
*/
UITexturePacker bp = new UITexturePacker(width, height, false);
Storage[] storage = new Storage[textures.Length];
for (int i = 0; i < textures.Length; i++)
{
Texture2D tex = textures[i];
if (!tex) continue;
Rect rect = new Rect();
int xPadding = 1;
int yPadding = 1;
for (xPadding = 1; xPadding >= 0; --xPadding)
{
for (yPadding = 1; yPadding >= 0; --yPadding)
{
rect = bp.Insert(tex.width + (xPadding * padding), tex.height + (yPadding * padding),
UITexturePacker.FreeRectChoiceHeuristic.RectBestAreaFit);
if (rect.width != 0 && rect.height != 0) break;
// After having no padding if it still doesn't fit -- increase texture size.
else if (xPadding == 0 && yPadding == 0)
{
return PackTextures(texture, textures, width * (width <= height ? 2 : 1),
height * (height < width ? 2 : 1), padding, maxSize);
}
}
if (rect.width != 0 && rect.height != 0) break;
}
storage[i] = new Storage();
storage[i].rect = rect;
storage[i].paddingX = (xPadding != 0);
storage[i].paddingY = (yPadding != 0);
}
texture.Resize(width, height);
texture.SetPixels(new Color[width * height]);
// The returned rects
Rect[] rects = new Rect[textures.Length];
for (int i = 0; i < textures.Length; i++)
{
Texture2D tex = textures[i];
if (!tex) continue;
Rect rect = storage[i].rect;
int xPadding = (storage[i].paddingX ? padding : 0);
int yPadding = (storage[i].paddingY ? padding : 0);
Color[] colors = tex.GetPixels();
// Would be used to rotate the texture if need be.
if (rect.width != tex.width + xPadding)
{
Color[] newColors = tex.GetPixels();
for (int x = 0; x < rect.width; x++)
{
for (int y = 0; y < rect.height; y++)
{
int prevIndex = ((int)rect.height - (y + 1)) + x * (int)tex.width;
newColors[x + y * (int)rect.width] = colors[prevIndex];
}
}
colors = newColors;
}
texture.SetPixels((int)rect.x, (int)rect.y, (int)rect.width - xPadding, (int)rect.height - yPadding, colors);
rect.x /= width;
rect.y /= height;
rect.width = (rect.width - xPadding) / width;
rect.height = (rect.height - yPadding) / height;
rects[i] = rect;
}
texture.Apply();
return rects;
}
public Rect Insert (int width, int height, FreeRectChoiceHeuristic method)
{
Rect newNode = new Rect();
int score1 = 0; // Unused in this function. We don't need to know the score after finding the position.
int score2 = 0;
switch (method)
{
case FreeRectChoiceHeuristic.RectBestShortSideFit: newNode = FindPositionForNewNodeBestShortSideFit(width, height, ref score1, ref score2); break;
case FreeRectChoiceHeuristic.RectBottomLeftRule: newNode = FindPositionForNewNodeBottomLeft(width, height, ref score1, ref score2); break;
case FreeRectChoiceHeuristic.RectContactPointRule: newNode = FindPositionForNewNodeContactPoint(width, height, ref score1); break;
case FreeRectChoiceHeuristic.RectBestLongSideFit: newNode = FindPositionForNewNodeBestLongSideFit(width, height, ref score2, ref score1); break;
case FreeRectChoiceHeuristic.RectBestAreaFit: newNode = FindPositionForNewNodeBestAreaFit(width, height, ref score1, ref score2); break;
}
if (newNode.height == 0)
return newNode;
int numRectanglesToProcess = freeRectangles.Count;
for (int i = 0; i < numRectanglesToProcess; ++i)
{
if (SplitFreeNode(freeRectangles[i], ref newNode))
{
freeRectangles.RemoveAt(i);
--i;
--numRectanglesToProcess;
}
}
PruneFreeList();
usedRectangles.Add(newNode);
return newNode;
}
public void Insert (List<Rect> rects, List<Rect> dst, FreeRectChoiceHeuristic method)
{
dst.Clear();
while (rects.Count > 0)
{
int bestScore1 = int.MaxValue;
int bestScore2 = int.MaxValue;
int bestRectIndex = -1;
Rect bestNode = new Rect();
for (int i = 0; i < rects.Count; ++i)
{
int score1 = 0;
int score2 = 0;
Rect newNode = ScoreRect((int)rects[i].width, (int)rects[i].height, method, ref score1, ref score2);
if (score1 < bestScore1 || (score1 == bestScore1 && score2 < bestScore2))
{
bestScore1 = score1;
bestScore2 = score2;
bestNode = newNode;
bestRectIndex = i;
}
}
if (bestRectIndex == -1)
return;
PlaceRect(bestNode);
rects.RemoveAt(bestRectIndex);
}
}
void PlaceRect (Rect node)
{
int numRectanglesToProcess = freeRectangles.Count;
for (int i = 0; i < numRectanglesToProcess; ++i)
{
if (SplitFreeNode(freeRectangles[i], ref node))
{
freeRectangles.RemoveAt(i);
--i;
--numRectanglesToProcess;
}
}
PruneFreeList();
usedRectangles.Add(node);
}
Rect ScoreRect (int width, int height, FreeRectChoiceHeuristic method, ref int score1, ref int score2)
{
Rect newNode = new Rect();
score1 = int.MaxValue;
score2 = int.MaxValue;
switch (method)
{
case FreeRectChoiceHeuristic.RectBestShortSideFit: newNode = FindPositionForNewNodeBestShortSideFit(width, height, ref score1, ref score2); break;
case FreeRectChoiceHeuristic.RectBottomLeftRule: newNode = FindPositionForNewNodeBottomLeft(width, height, ref score1, ref score2); break;
case FreeRectChoiceHeuristic.RectContactPointRule: newNode = FindPositionForNewNodeContactPoint(width, height, ref score1);
score1 = -score1; // Reverse since we are minimizing, but for contact point score bigger is better.
break;
case FreeRectChoiceHeuristic.RectBestLongSideFit: newNode = FindPositionForNewNodeBestLongSideFit(width, height, ref score2, ref score1); break;
case FreeRectChoiceHeuristic.RectBestAreaFit: newNode = FindPositionForNewNodeBestAreaFit(width, height, ref score1, ref score2); break;
}
// Cannot fit the current rectangle.
if (newNode.height == 0)
{
score1 = int.MaxValue;
score2 = int.MaxValue;
}
return newNode;
}
/// Computes the ratio of used surface area.
public float Occupancy ()
{
ulong usedSurfaceArea = 0;
for (int i = 0; i < usedRectangles.Count; ++i)
usedSurfaceArea += (uint)usedRectangles[i].width * (uint)usedRectangles[i].height;
return (float)usedSurfaceArea / (binWidth * binHeight);
}
Rect FindPositionForNewNodeBottomLeft (int width, int height, ref int bestY, ref int bestX)
{
Rect bestNode = new Rect();
//memset(bestNode, 0, sizeof(Rect));
bestY = int.MaxValue;
for (int i = 0; i < freeRectangles.Count; ++i)
{
// Try to place the rectangle in upright (non-flipped) orientation.
if (freeRectangles[i].width >= width && freeRectangles[i].height >= height)
{
int topSideY = (int)freeRectangles[i].y + height;
if (topSideY < bestY || (topSideY == bestY && freeRectangles[i].x < bestX))
{
bestNode.x = freeRectangles[i].x;
bestNode.y = freeRectangles[i].y;
bestNode.width = width;
bestNode.height = height;
bestY = topSideY;
bestX = (int)freeRectangles[i].x;
}
}
if (allowRotations && freeRectangles[i].width >= height && freeRectangles[i].height >= width)
{
int topSideY = (int)freeRectangles[i].y + width;
if (topSideY < bestY || (topSideY == bestY && freeRectangles[i].x < bestX))
{
bestNode.x = freeRectangles[i].x;
bestNode.y = freeRectangles[i].y;
bestNode.width = height;
bestNode.height = width;
bestY = topSideY;
bestX = (int)freeRectangles[i].x;
}
}
}
return bestNode;
}
Rect FindPositionForNewNodeBestShortSideFit (int width, int height, ref int bestShortSideFit, ref int bestLongSideFit)
{
Rect bestNode = new Rect();
//memset(&bestNode, 0, sizeof(Rect));
bestShortSideFit = int.MaxValue;
for (int i = 0; i < freeRectangles.Count; ++i)
{
// Try to place the rectangle in upright (non-flipped) orientation.
if (freeRectangles[i].width >= width && freeRectangles[i].height >= height)
{
int leftoverHoriz = Mathf.Abs((int)freeRectangles[i].width - width);
int leftoverVert = Mathf.Abs((int)freeRectangles[i].height - height);
int shortSideFit = Mathf.Min(leftoverHoriz, leftoverVert);
int longSideFit = Mathf.Max(leftoverHoriz, leftoverVert);
if (shortSideFit < bestShortSideFit || (shortSideFit == bestShortSideFit && longSideFit < bestLongSideFit))
{
bestNode.x = freeRectangles[i].x;
bestNode.y = freeRectangles[i].y;
bestNode.width = width;
bestNode.height = height;
bestShortSideFit = shortSideFit;
bestLongSideFit = longSideFit;
}
}
if (allowRotations && freeRectangles[i].width >= height && freeRectangles[i].height >= width)
{
int flippedLeftoverHoriz = Mathf.Abs((int)freeRectangles[i].width - height);
int flippedLeftoverVert = Mathf.Abs((int)freeRectangles[i].height - width);
int flippedShortSideFit = Mathf.Min(flippedLeftoverHoriz, flippedLeftoverVert);
int flippedLongSideFit = Mathf.Max(flippedLeftoverHoriz, flippedLeftoverVert);
if (flippedShortSideFit < bestShortSideFit || (flippedShortSideFit == bestShortSideFit && flippedLongSideFit < bestLongSideFit))
{
bestNode.x = freeRectangles[i].x;
bestNode.y = freeRectangles[i].y;
bestNode.width = height;
bestNode.height = width;
bestShortSideFit = flippedShortSideFit;
bestLongSideFit = flippedLongSideFit;
}
}
}
return bestNode;
}
Rect FindPositionForNewNodeBestLongSideFit (int width, int height, ref int bestShortSideFit, ref int bestLongSideFit)
{
Rect bestNode = new Rect();
//memset(&bestNode, 0, sizeof(Rect));
bestLongSideFit = int.MaxValue;
for (int i = 0; i < freeRectangles.Count; ++i)
{
// Try to place the rectangle in upright (non-flipped) orientation.
if (freeRectangles[i].width >= width && freeRectangles[i].height >= height)
{
int leftoverHoriz = Mathf.Abs((int)freeRectangles[i].width - width);
int leftoverVert = Mathf.Abs((int)freeRectangles[i].height - height);
int shortSideFit = Mathf.Min(leftoverHoriz, leftoverVert);
int longSideFit = Mathf.Max(leftoverHoriz, leftoverVert);
if (longSideFit < bestLongSideFit || (longSideFit == bestLongSideFit && shortSideFit < bestShortSideFit))
{
bestNode.x = freeRectangles[i].x;
bestNode.y = freeRectangles[i].y;
bestNode.width = width;
bestNode.height = height;
bestShortSideFit = shortSideFit;
bestLongSideFit = longSideFit;
}
}
if (allowRotations && freeRectangles[i].width >= height && freeRectangles[i].height >= width)
{
int leftoverHoriz = Mathf.Abs((int)freeRectangles[i].width - height);
int leftoverVert = Mathf.Abs((int)freeRectangles[i].height - width);
int shortSideFit = Mathf.Min(leftoverHoriz, leftoverVert);
int longSideFit = Mathf.Max(leftoverHoriz, leftoverVert);
if (longSideFit < bestLongSideFit || (longSideFit == bestLongSideFit && shortSideFit < bestShortSideFit))
{
bestNode.x = freeRectangles[i].x;
bestNode.y = freeRectangles[i].y;
bestNode.width = height;
bestNode.height = width;
bestShortSideFit = shortSideFit;
bestLongSideFit = longSideFit;
}
}
}
return bestNode;
}
Rect FindPositionForNewNodeBestAreaFit (int width, int height, ref int bestAreaFit, ref int bestShortSideFit)
{
Rect bestNode = new Rect();
//memset(&bestNode, 0, sizeof(Rect));
bestAreaFit = int.MaxValue;
for (int i = 0; i < freeRectangles.Count; ++i)
{
int areaFit = (int)freeRectangles[i].width * (int)freeRectangles[i].height - width * height;
// Try to place the rectangle in upright (non-flipped) orientation.
if (freeRectangles[i].width >= width && freeRectangles[i].height >= height)
{
int leftoverHoriz = Mathf.Abs((int)freeRectangles[i].width - width);
int leftoverVert = Mathf.Abs((int)freeRectangles[i].height - height);
int shortSideFit = Mathf.Min(leftoverHoriz, leftoverVert);
if (areaFit < bestAreaFit || (areaFit == bestAreaFit && shortSideFit < bestShortSideFit))
{
bestNode.x = freeRectangles[i].x;
bestNode.y = freeRectangles[i].y;
bestNode.width = width;
bestNode.height = height;
bestShortSideFit = shortSideFit;
bestAreaFit = areaFit;
}
}
if (allowRotations && freeRectangles[i].width >= height && freeRectangles[i].height >= width)
{
int leftoverHoriz = Mathf.Abs((int)freeRectangles[i].width - height);
int leftoverVert = Mathf.Abs((int)freeRectangles[i].height - width);
int shortSideFit = Mathf.Min(leftoverHoriz, leftoverVert);
if (areaFit < bestAreaFit || (areaFit == bestAreaFit && shortSideFit < bestShortSideFit))
{
bestNode.x = freeRectangles[i].x;
bestNode.y = freeRectangles[i].y;
bestNode.width = height;
bestNode.height = width;
bestShortSideFit = shortSideFit;
bestAreaFit = areaFit;
}
}
}
return bestNode;
}
/// Returns 0 if the two intervals i1 and i2 are disjoint, or the length of their overlap otherwise.
int CommonIntervalLength (int i1start, int i1end, int i2start, int i2end)
{
if (i1end < i2start || i2end < i1start)
return 0;
return Mathf.Min(i1end, i2end) - Mathf.Max(i1start, i2start);
}
int ContactPointScoreNode (int x, int y, int width, int height)
{
int score = 0;
if (x == 0 || x + width == binWidth)
score += height;
if (y == 0 || y + height == binHeight)
score += width;
for (int i = 0; i < usedRectangles.Count; ++i)
{
if (usedRectangles[i].x == x + width || usedRectangles[i].x + usedRectangles[i].width == x)
score += CommonIntervalLength((int)usedRectangles[i].y, (int)usedRectangles[i].y + (int)usedRectangles[i].height, y, y + height);
if (usedRectangles[i].y == y + height || usedRectangles[i].y + usedRectangles[i].height == y)
score += CommonIntervalLength((int)usedRectangles[i].x, (int)usedRectangles[i].x + (int)usedRectangles[i].width, x, x + width);
}
return score;
}
Rect FindPositionForNewNodeContactPoint (int width, int height, ref int bestContactScore)
{
Rect bestNode = new Rect();
//memset(&bestNode, 0, sizeof(Rect));
bestContactScore = -1;
for (int i = 0; i < freeRectangles.Count; ++i)
{
// Try to place the rectangle in upright (non-flipped) orientation.
if (freeRectangles[i].width >= width && freeRectangles[i].height >= height)
{
int score = ContactPointScoreNode((int)freeRectangles[i].x, (int)freeRectangles[i].y, width, height);
if (score > bestContactScore)
{
bestNode.x = (int)freeRectangles[i].x;
bestNode.y = (int)freeRectangles[i].y;
bestNode.width = width;
bestNode.height = height;
bestContactScore = score;
}
}
if (allowRotations && freeRectangles[i].width >= height && freeRectangles[i].height >= width)
{
int score = ContactPointScoreNode((int)freeRectangles[i].x, (int)freeRectangles[i].y, height, width);
if (score > bestContactScore)
{
bestNode.x = (int)freeRectangles[i].x;
bestNode.y = (int)freeRectangles[i].y;
bestNode.width = height;
bestNode.height = width;
bestContactScore = score;
}
}
}
return bestNode;
}
bool SplitFreeNode (Rect freeNode, ref Rect usedNode)
{
// Test with SAT if the rectangles even intersect.
if (usedNode.x >= freeNode.x + freeNode.width || usedNode.x + usedNode.width <= freeNode.x ||
usedNode.y >= freeNode.y + freeNode.height || usedNode.y + usedNode.height <= freeNode.y)
return false;
if (usedNode.x < freeNode.x + freeNode.width && usedNode.x + usedNode.width > freeNode.x)
{
// New node at the top side of the used node.
if (usedNode.y > freeNode.y && usedNode.y < freeNode.y + freeNode.height)
{
Rect newNode = freeNode;
newNode.height = usedNode.y - newNode.y;
freeRectangles.Add(newNode);
}
// New node at the bottom side of the used node.
if (usedNode.y + usedNode.height < freeNode.y + freeNode.height)
{
Rect newNode = freeNode;
newNode.y = usedNode.y + usedNode.height;
newNode.height = freeNode.y + freeNode.height - (usedNode.y + usedNode.height);
freeRectangles.Add(newNode);
}
}
if (usedNode.y < freeNode.y + freeNode.height && usedNode.y + usedNode.height > freeNode.y)
{
// New node at the left side of the used node.
if (usedNode.x > freeNode.x && usedNode.x < freeNode.x + freeNode.width)
{
Rect newNode = freeNode;
newNode.width = usedNode.x - newNode.x;
freeRectangles.Add(newNode);
}
// New node at the right side of the used node.
if (usedNode.x + usedNode.width < freeNode.x + freeNode.width)
{
Rect newNode = freeNode;
newNode.x = usedNode.x + usedNode.width;
newNode.width = freeNode.x + freeNode.width - (usedNode.x + usedNode.width);
freeRectangles.Add(newNode);
}
}
return true;
}
void PruneFreeList ()
{
for (int i = 0; i < freeRectangles.Count; ++i)
for (int j = i + 1; j < freeRectangles.Count; ++j)
{
if (IsContainedIn(freeRectangles[i], freeRectangles[j]))
{
freeRectangles.RemoveAt(i);
--i;
break;
}
if (IsContainedIn(freeRectangles[j], freeRectangles[i]))
{
freeRectangles.RemoveAt(j);
--j;
}
}
}
bool IsContainedIn (Rect a, Rect b)
{
return a.x >= b.x && a.y >= b.y
&& a.x + a.width <= b.x + b.width
&& a.y + a.height <= b.y + b.height;
}
}
[Unity3D] 5.0 图集合并扩展工具,用于解决UGUI与AssetBundle打包造成资源包过大的问题的更多相关文章
- font-size:0的妙用,用于解决inline或者inline-block造成的间隙
1.图片间的缝隙(图片间的间隙一般是由换行.缩进造成的) <div> <img src="1.jpg"> <img src="2.jpg&q ...
- c#3.0提供的扩展方法
在c#3.0之前,想要为内置的类型添加一个方法显然是不可能的.但是,c#3.0提供的扩展方法可以解决这个问题.具体代码如下: public static class ExtendedClass {pu ...
- Unity3D 5.0版本+注册工具分享
Unity3D引擎5.0正式版本发布也有一段时间了.笔者今天下载了新版本顺便分享一下资源. 主要有两个资源,一个是5.0f4的官方客户端,另外一个是vs的调试插件.有需要的盆友就拿去.都在下面的连接地 ...
- jQuery源码分析-03扩展工具函数jQuery.extend
// 扩展工具函数 jQuery.extend({ // http://www.w3school.com.cn/jquery/core_noconflict.asp // 释放$的 jQuery 控制 ...
- Unity3d项目合作 场景的合并和还原
Unity3d项目合作 场景的合并和还原 特别声明:转载自Unity3D研究院 如何侵犯版权,请通知我删除! 摘要: 导出Unity场景的所有游戏对象信息,一种是XML一种是JSON.本篇文章我们把 ...
- HBase2.0中的Benchmark工具 — PerformanceEvaluation
简介 在项目开发过程中,我们经常需要一些benchmark工具来对系统进行压测,以获得系统的性能参数,极限吞吐等等指标. 而在HBase中,就自带了一个benchmark工具—PerformanceE ...
- (转载)详解7.0带来的新工具类:DiffUtil
[Android]详解7.0带来的新工具类:DiffUtil 标签: diffutil 2017-04-17 18:21 226人阅读 评论(0) 收藏 举报 分类: Android学习笔记(94) ...
- PHP的LZF压缩扩展工具
这次为大家带来的是另外一个 PHP 的压缩扩展,当然也是非常冷门的一种压缩格式,所以使用的人会比较少,而且在 PHP 中提供的相关的函数也只是对字符串的编码与解码,并没有针对文件的操作.因此,就像 B ...
- pycharm中添加扩展工具pylint
今天调试了好几个小时,想吧pylint集成到pycharm中去,从网上找了个宝贝帖 子,但是不好用,原因是作者写的脚本是检查工程和模块的,而我的是单独检查一个文件,当然前者肯定会在项目后期用的.所以就 ...
随机推荐
- java 中的拦截器和过滤器
区别: 1.拦截器是基于java的反射机制的,而过滤器是基于函数回调 2.过滤器依赖与servlet容器,而拦截器不依赖与servlet容器 3.拦截器只能对action请求起作用,而过滤器则可以对几 ...
- 从HTTP请求中获取客户IP地址
/** * 从HTTP请求中获取客户IP地址 * * @param request http请求 * @return 客户IP地址 */ public s ...
- python3 字符串属性(一)
python3 字符串属性 >>> a='hello world' >>> dir(a) ['__add__', '__class__', '__contains_ ...
- C#返回Json,js解析Json,并添加到select标签中
后台: List<Student> list=GetAll();//id name string json = new JavaScriptSerializer().Serialize(l ...
- 关于unity3D的GL图像库的使用
GL图象库 GL图象库是底层的图象库,主要功能是使用程序来绘制常见的2D与3D几何图形.这些图形具有一定的特殊性,他们不属于3D网格图形,只会以面的形式渲染.使用GL图象库,可在屏幕中绘制2D几何图形 ...
- Java企业微信开发_07_JSSDK多图上传
一.本节要点 1.1可信域名 所有的JS接口只能在企业微信应用的可信域名下调用(包括子域名),可在企业微信的管理后台“我的应用”里设置应用可信域名.这个域名必须要通过ICP备案,不然jssdk会配置失 ...
- codeforces 632A A. Grandma Laura and Apples(暴力)
A. Grandma Laura and Apples time limit per test 1 second memory limit per test 256 megabytes input s ...
- windows 7下mingw+msys编译ffmpeg
windows 7下mingw+msys编译ffmpeg 1-->下载安装MingW,mingw-get-inst-20120426.exe http://sourceforge.ne ...
- CH5103 [NOIP2008]传纸条[线性DP]
给定一个 N*M 的矩阵A,每个格子中有一个整数.现在需要找到两条从左上角 (1,1) 到右下角 (N,M) 的路径,路径上的每一步只能向右或向下走.路径经过的格子中的数会被取走.两条路径不能经过同一 ...
- Git远程克隆仓库出现Permission denied (publickey)
$ git clone git@github.com:DavidWanderer/test1.git Cloning into 'test1'... Warning: Permanently adde ...