[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中去,从网上找了个宝贝帖 子,但是不好用,原因是作者写的脚本是检查工程和模块的,而我的是单独检查一个文件,当然前者肯定会在项目后期用的.所以就 ...
随机推荐
- HTML5坦克大战1
在JavaScript中,不要在变量为定义之前去使用,这样很难察觉并且无法运行. 颜色不对. 当我的坦克移动时,敌人坦克消失. tankGame3.html <!DOCTYPE html> ...
- 微信等webview中无法修改document.title的情况
// hack在微信等webview中无法修改document.title的情况 var $iframe = $('<iframe src="https://www.bbtree.co ...
- python字符串相关操作
字符串搜索相关搜索指定字符串,没有返回-1:str.find('t')指定起始位置搜索:str.find('t',start)指定起始及结束位置搜索:str.find('t',start,end)从右 ...
- ffmpeg用法(心得体会还有你见过的用法)
ffmpeg的常用用法很多,我这里提供的用法有可能有许多地方是你没见过的. 一.ffmpeg合并视频 我经常需要切割再把一些零碎的视频给拼接起来,这样可以省许多磁盘空间.其实用mencoder挺不错的 ...
- 2017-2018-1 20179203 《Linux内核原理与分析》第四周作业
攥写人:李鹏举 学号:20179203 ( 原创作品转载请注明出处) ( 学习课程:<Linux内核分析>MOOC课程http://mooc.study.163.com/course/US ...
- HDOJ1728(限制转弯的迷宫问题)
用bfs进行深搜,求出每个可达点的最小转弯数 #include<cstdio> #include<cstring> #include<queue> using na ...
- java基础知识(2)---语法基础
二:java语法基础: 1,关键字:其实就是某种语言赋予了特殊含义的单词. 保留字:其实就是还没有赋予特殊含义,但是准备日后要使用过的单词. 2,标示符:其实就是在程序中自定义的名词.比如类名,变量名 ...
- JVM类加载(4)—加载器
定义: 虚拟机设计团队把类加载阶段中“通过一个类的全限定名来获取描述此类的二进制字节流”这个动作放到虚拟机外部去实现,以便让应用程序自己决定如何去获取所需要的类.实现这个动作的代码模块称之为“类加载器 ...
- RAC环境下ORACLE序列缓存导致序列混乱
目前项目中发现了这样一个问题,在数据库部署了RAC环境之后,偶尔会出现从Oracle Sequence所取出来的数是混乱的,比如第二次比第一次所取的数要小.这样当程序的逻辑依赖于ID的大小来排序时,就 ...
- Project Online JS 添加Ribbon按钮
var Projects = Projects || {}; (function () { Projects.ribbonButtonClick = function (name) { var pro ...