说到UI优化,很多人对其并不以为意,UI的制作无非使用UGUI或者NGUI。UI优化主要是针对图集,还有一些依赖项的优化,针对的是内存优化,上面这些都是关于静态UI的优化,这个是作为程序员都要经历的阶段。其实很多开发者对使用UGUI或者NGUI已经墨守成规了,我们如果真想去优化,其实完全可以自己封装一套UI的底层代码,效率很高的,摒弃了UGUI和NGUI影响效率的代码,自己封装的简单实用,在我的视频课程——飞车竞速
在这个游戏中使用的UI都是自己封装的,游戏 UI底层代码封装如下所示:
这个UI底层封装的是针对我们项目的需求开发的,在这里是给读者拓展一下思路,UI优化并不仅限于图集和图片大小的优化,代码层面也是可以做的。
以上给读者介绍的都是关于静态UI的制作与优化,本篇博客主要是给读者介绍一下关于UI的动态图片优化,首先明白什么是动态加载图片,比如我们创建的角色ICON,还有游戏生成的怪物ICON等等,就是说这些ICON不是事先就有的,而是根据游戏动态加载到场景中的,我们的优化就是针对这些动态加载的ICON去做优化,在程序运行的过程中打图集,下面我们先实现一个2D动作播放的动态图集生成的Demo。
在Unity中如下图所示:
上图就是我们常见的序列帧动画,它的制作方式很多,我们可以事先将其打成图集,按照顺序播放,也可以直接播放,我们接下来实现的是动态的加载,动态的打图集算法。我们实现的思路是首先获取到需要打图集的图片,然后将其按照顺序依次在已创建的空白图片上把序列帧图片粘贴上,同时我们还需要使用一个Json文件记录我们对应图片的信息,这个信息非常重要,它主要目的是帮助我们找到对应的图片,这个信息包含:图片所在位置,图片的宽,高,图片的名字。
这样我们就完成了图集的动态生成,接下来就是逻辑代码的编写了。
第一步:获取动态加载的文件,在这里我们是将其拷贝到一个指定的目录下面,方便我们打图集,这个目录我们自己指定,就是模拟程序运行时动态获取到图片,然后去加载,代码如下:
CopyPasteFoldersAndPNG(Application.dataPath + "/SpriteGenerator/Demos/Sprites", Application.persistentDataPath);
string[] files = Directory.GetFiles(Application.persistentDataPath + "/Textures", "*.png");
第二步:定义图集大小,将获取到的图片在图集上进行排列, 核心算法如下:
protected IEnumerator createPack(string savePath = "") {
if (savePath != "") {
if (deletePreviousCacheVersion && Directory.Exists(Application.persistentDataPath + "/AssetPacker/" + cacheName + "/"))
foreach (string dirPath in Directory.GetDirectories(Application.persistentDataPath + "/AssetPacker/" + cacheName + "/", "*", SearchOption.AllDirectories))
Directory.Delete(dirPath, true);
Directory.CreateDirectory(savePath);
}
List<Texture2D> textures = new List<Texture2D>();
List<string> images = new List<string>();
foreach (TextureToPack itemToRaster in itemsToRaster) {
WWW loader = new WWW("file:///" + itemToRaster.file);
yield return loader;
textures.Add(loader.texture);
images.Add(itemToRaster.id);
}
int textureSize = allow4096Textures ? 4096 : 2048;
List<Rect> rectangles = new List<Rect>();
for (int i = 0; i < textures.Count; i++)
if (textures[i].width > textureSize || textures[i].height > textureSize)
throw new Exception("A texture size is bigger than the sprite sheet size!");
else
rectangles.Add(new Rect(0, 0, textures[i].width, textures[i].height));
const int padding = 1;
int numSpriteSheet = 0;
while (rectangles.Count > 0) {
Texture2D texture = new Texture2D(textureSize, textureSize, TextureFormat.ARGB32, false);
Color32[] fillColor = texture.GetPixels32();
for (int i = 0; i < fillColor.Length; ++i)
fillColor[i] = Color.clear;
RectanglePacker packer = new RectanglePacker(texture.width, texture.height, padding);
for (int i = 0; i < rectangles.Count; i++)
packer.insertRectangle((int) rectangles[i].width, (int) rectangles[i].height, i);
packer.packRectangles();
if (packer.rectangleCount > 0) {
texture.SetPixels32(fillColor);
IntegerRectangle rect = new IntegerRectangle();
List<TextureAsset> textureAssets = new List<TextureAsset>();
List<Rect> garbageRect = new List<Rect>();
List<Texture2D> garabeTextures = new List<Texture2D>();
List<string> garbageImages = new List<string>();
for (int j = 0; j < packer.rectangleCount; j++) {
rect = packer.getRectangle(j, rect);
int index = packer.getRectangleId(j);
texture.SetPixels32(rect.x, rect.y, rect.width, rect.height, textures[index].GetPixels32());
TextureAsset textureAsset = new TextureAsset();
textureAsset.x = rect.x;
textureAsset.y = rect.y;
textureAsset.width = rect.width;
textureAsset.height = rect.height;
textureAsset.name = images[index];
textureAssets.Add(textureAsset);
garbageRect.Add(rectangles[index]);
garabeTextures.Add(textures[index]);
garbageImages.Add(images[index]);
}
foreach (Rect garbage in garbageRect)
rectangles.Remove(garbage);
foreach (Texture2D garbage in garabeTextures)
textures.Remove(garbage);
foreach (string garbage in garbageImages)
images.Remove(garbage);
texture.Apply();
if (savePath != "") {
File.WriteAllBytes(savePath + "/data" + numSpriteSheet + ".png", texture.EncodeToPNG());
File.WriteAllText(savePath + "/data" + numSpriteSheet + ".json", JsonUtility.ToJson(new TextureAssets(textureAssets.ToArray())));
++numSpriteSheet;
}
foreach (TextureAsset textureAsset in textureAssets)
mSprites.Add(textureAsset.name, Sprite.Create(texture, new Rect(textureAsset.x, textureAsset.y, textureAsset.width, textureAsset.height), Vector2.zero, pixelsPerUnit, 0, SpriteMeshType.FullRect));
}
}
OnProcessCompleted.Invoke();
}
代码虽然比较长,但是理解起来不难,没有用任何算法,它只是按照顺序去排列,然后保存成图集和json文件,最终的图集如下所示:
Json文件如下所示:
{"assets":[{"x":0,"y":0,"width":286,"height":387,"name":"walking0004"},{"x":0,"y":388,"width":286,"height":387,"name":"walking0005"},{"x":0,"y":776,"width":286,"height":387,"name":"walking0003"},{"x":0,"y":1164,"width":286,"height":387,"name":"walking0001"},{"x":0,"y":1552,"width":286,"height":387,"name":"walking0002"},{"x":287,"y":0,"width":286,"height":387,"name":"walking0006"},{"x":287,"y":388,"width":286,"height":387,"name":"walking0010"},{"x":287,"y":776,"width":286,"height":387,"name":"walking0011"},{"x":287,"y":1164,"width":286,"height":387,"name":"walking0009"},{"x":287,"y":1552,"width":286,"height":387,"name":"walking0007"},{"x":574,"y":0,"width":286,"height":387,"name":"walking0008"}]}
接下来就是播放动画了,也就是最后一步。
第三步:播放动画逻辑代码实现如下:
IEnumerator LoadAnimation() {
Sprite[] sprites = assetPacker.GetSprites("walking");
int j = 0;
while (j < sprites.Length) {
anim.sprite = sprites[j++];
yield return new WaitForSeconds(0.1f);
if (j == sprites.Length)
j = 0;
}
}
实现效果如下所示:
这样就完成了动态图集的生成,当然我们还可以生成大的图集,这个一般在端游中使用的,效果如下图所示:
以上是关于动态图集的生成,我们测试了一下实现500个矩形,类似500个小ICON,花费时间是8ms,非常理想。该技术可以应用到项目开发中。。。。。。
项目下载地址:链接:https://pan.baidu.com/s/1bV6hepy2H6t1Xnzg88JOdg 密码:tybj