(前面是废话,可以略过,分割线下面是内容)
前两天研究完了LOD 和 MipMap 感觉很简单,实现起来不用多久。
但是发现如果将某些功能运用在商业代码上,为了提高效率,要做很多前期工作。
这几天一直在学习游戏开发优化方面的技巧,感觉都很片面,实际的LOD 和 MipMap这些功能只有在一些需要大量摄像机移动的场景用的比较多
固定视角或者是锁死深度的游戏 其实这方面用的比较少,而目前市场上大部分都是锁死视角或者锁死深度的游戏。。
于是这两天重点去研究了下关于图集的优化。
优化图集的目的是为了降低DrawCall的额外产生,在设计UI框架的时候,就要考虑好文件结构和图集的处理方式了。
我目前工作的 项目采用的是通用作法,把每个模块单独打一个图集,特别大的背景图则单独加载。
在学习打图集的过程中,有一个很深刻的感受。。我发现了很多没听说过的技术,牵根连枝,拔一根萝卜带起一片泥那种。
我想学打图集以及图集的规划和设计,但是项目模块很多,跟UI,图片产生关系的工作量都非常的大。如果人工做这件事情需要消耗很大的精力
所以我在想学图集的时候,不得不学习怎么自动化打图集。于是看起了项目工程里面的TexturePacker工具。非常方便,于是也想做一个。
在学习TexturePacker的时候,又发现了一个问题,我想要批量打包,TexturePacker只能做到把碎图拼成一张整图,如果想要在项目里面方便的使用
远远不够,人力操作还有一大部分没有被提取出来。但是由于是试用期,很多代码没有公开给我,只能自己网上查办法了。
于是又找到了UnityEditor 自动化切图的办法。其实这两步是必不可少的,只是开始无知,想不上去。以为是那个软件自带的全面功能。。
然后我就从学习怎么自动切图开始了。
好了废话说完了,下面开始介绍自动化打包图集的流程
=======================================分割线===============================================
全自动打图集的流程:
美术规范:建议给碎图,因为我们需要用texturepacker来整合碎图,这个工具最方便的地方是提供了碎图的同时还提供了每个碎图在大图的具体坐标、大小信息
1:是使用texutrepacker 进行统一化整理和获取碎图的信息 是一个xx.png 和一个xxplist.txt的文本 文本采用json的方式方便我们读写,这里用批处理的方式将文件自动存放指定的文件夹
使用到的功能:
Selection.GetFiltered(typeof(Object), SelectionMode.Assets)
获取我们想要的路劲。
然后使用C#的Process类,进行批处理操作,代替人工导出
Process process = new Process();//创建一个进程 ProcessStartInfo info = new ProcessStartInfo("texturepacker"); //设置进程信息 string arguments = "Assets/Test/";//这里推荐安装好texturepacker的同学使用cmd命令行查看下他的使用参数,这里就是我们要填的api参数 这个代表我们选着导哪个文件夹的图片 arguments += " --sheet " + path + "/"+selecteFileName+".png";//导出目录.png arguments += " --data " + path+ "/"+selecteFileName+".txt"; //导出目录.txt arguments += " --format unity"; //选择格式(平台) arguments += " --max-size 1024"; //选择texture的maxsize arguments += " --disable-rotation";//关闭旋转,据说不关闭可能会碰到坑,还没研究这个参数 info.Arguments = arguments; info.CreateNoWindow = false;//打开运行窗口(装逼用的 就是一个dos界面 和bat那种一样) UnityEngine.Debug.Log(info.Arguments); Process.Start(info);//开始执行
2:开始切分整图
这里面也是2个步骤
一个是解析json
一个是设置TextureImporter属性,因为自动化,就要取代所有的手动操作,所以每一步都需要用代码实现
这里涉及的就是TextureImporter这个类 和 SpriteMetaData 这个类了(如果不是我今天需求这个,这俩东西我听都没听说过 = =)
由于这里的代码比较多,就不贴局部代码了,下面我会贴整个代码的。
切分的时候有一个坑,因为texturepacker是用的左上角为坐标原点,而Unity的gui使用的是左下角为左边点,如果不做坐标系转换的话,切出来的东西根本不能用。
我想过很多种方式,目前好像只有这种方式涉及的代码量比较少,不过开销稍微大一点,不过考虑到编辑器只是开发人员会使用,也无关紧要啦~
如果要坐标转换的话,需要知道texture的原始大小。因为我们默认导入的比例都是1:1的,不会缩放。所以不用考虑缩放带来的误差。但是TextureImporter里面完全没有关于texture大小的属性
这时候我想过2种方案
第一种是不用获取texture大小, 在texture的面板上面是有EditorSprite这个功能的,这个可以根据alpha值做自动切分。本着Unity有的功能,必然会有API接口的想法,掘地三尺,还是没找到。
在以前的版本倒是有一个接口,但是现在已经弃用了。找了半天之后放弃了。
第二种是找到获取texture大小的办法,想了很多,最后使用AssetDatabase.LoadAssetAtPath<Texture2D>(path)这个方式来加载我想要的这个texture ,然后读他的size属性来取值。。
算是野路子了= =不过学习到了新的一种加载资源的方式,不过这个方式只能在editor模式使用。
接下来我们就可以做偏移 对齐小图片啦~
3:切分整图。根据项目需求创建prefab作为图集的载体
这样一来就可以大功告成啦!
其实代码不多,开发难度也不大,就是在Unity整个知识体系里面比较冷门(个人觉得),所以整个过程中大部分时间是在找资料。
因为完全是自己爬坑,没有去照着公司项目代码去做,所以遇到了非常多的问题,也都克服了,所以写的有点啰嗦。。
下面是完整代码,没有做安全判定,因为完全是测试流程而已。
using System.Collections;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
using System.IO;
using System.Threading;
using System.Diagnostics;
public class BuildAtlas : MonoBehaviour
{
static string path;
static object wwwLoad;
[MenuItem("打包图集/打包")]
static void CreatTexturePacker()
{
Object[] arrayList = Selection.GetFiltered(typeof(Object), SelectionMode.Assets);//获取用户选择的物体
for (int i = 0; i < arrayList.Length; i++)
{
//用户如果选择多个文件夹路径则进行多次处理
path = AssetDatabase.GetAssetPath(arrayList[i]);
CallTexturePacker("test", path);
if (path != "")
{
float offest = GetTextureHeight(path + "/json.png");
TextureImporter textureImporter = AssetImporter.GetAtPath(path + "/json.png") as TextureImporter;
textureImporter.textureType = TextureImporterType.Sprite;
textureImporter.spriteImportMode = SpriteImportMode.Multiple;
textureImporter.GetDefaultPlatformTextureSettings();
WirteSpriteJsonToSheet(textureImporter, path + "/json.txt", offest);
textureImporter.SaveAndReimport();
}
}
}
/// <summary>
/// 获取目标texture的高度做偏移
/// </summary>
/// <param name="path"></param>
/// <returns></returns>
static float GetTextureHeight(string path)
{
UnityEngine.Debug.Log(path);
Texture2D texture2D = AssetDatabase.LoadAssetAtPath<Texture2D>(path);
return texture2D.width;
}
/// <summary>
/// 读取json文件
/// </summary>
/// <param name="path"></param>
/// <returns></returns>
static string GetJson(string path)
{
FileStream stream = new FileStream(path, FileMode.Open);
StreamReader sr = new StreamReader(stream);
string str = sr.ReadToEnd();
sr.Dispose();
sr.Close();
return str;
}
/// <summary>
/// 将TextureImprot按文本位置切分
/// </summary>
/// <param name="TIP"></param>
/// <param name="txtPath"></param>
/// <param name="offset"></param>
static void WirteSpriteJsonToSheet(TextureImporter TIP, string txtPath, float offset)
{
string spriteInfoArry = GetJson(txtPath);
Hashtable hashInfo = spriteInfoArry.hashtableFromJson();//转换成哈希表(很奇怪,转成arryList会返回空,可能是不太会用吧)
Hashtable frames = (Hashtable)hashInfo["frames"];
List<SpriteMetaData> Sprites = new List<SpriteMetaData>();
foreach (DictionaryEntry item in frames)
{
SpriteMetaData sprite = new SpriteMetaData();
Hashtable mTable = (Hashtable)item.Value; //每一步都要转成hashtable才能进行取值操作,得到的结果也要转成hashtable
Hashtable frame = (Hashtable)mTable["frame"];//取到位置信息
sprite.name = item.Key.ToString();
//这里要注意,json提供的坐标文件 坐标系是左上角为原点 UNITY的gui坐标系是左下角为原点,所以我们要做坐标转换,左上和左下,只需要做Y轴的转换即可
//转换的偏移量要和实际的texture对应,所以我们需要获取实际的Texture大小
sprite.rect = new Rect((float)frame["x"], offset - (float)frame["y"] - (float)frame["h"], (float)frame["w"], (float)frame["h"]);//位置信息赋值
Hashtable spriteSourceSize = (Hashtable)mTable["spriteSourceSize"];
Hashtable pviot = (Hashtable)mTable["sourceSize"];
sprite.border = new Vector4((float)spriteSourceSize["x"], (float)spriteSourceSize["y"], (float)spriteSourceSize["w"], (float)spriteSourceSize["h"]); //边框大小
sprite.alignment = 6;//Center = 0, TopLeft = 1, TopCenter = 2, TopRight = 3, LeftCenter = 4, RightCenter = 5, BottomLeft = 6, BottomCenter = 7, BottomRight = 8, Custom = 9.
sprite.pivot = new Vector2((float)pviot["w"], (float)pviot["h"]);
Sprites.Add(sprite);
}
TIP.spritesheet = Sprites.ToArray();
TIP.SaveAndReimport();
UnityEngine.Debug.Log("转换成功");
}
static void CallTexturePacker(string selecteFileName,string path)
{
Process process = new Process();
ProcessStartInfo info = new ProcessStartInfo("texturepacker");
string arguments = "Assets/Test/";
arguments += " --sheet " + path + "/"+selecteFileName+".png";
arguments += " --data " + path+ "/"+selecteFileName+".txt";
arguments += " --format unity";
arguments += " --pack-mode Best";
arguments += " --algorithm MaxRects";
arguments += " --max-size 1024";
arguments += " --trim-mode None";
arguments += " --disable-rotation";
arguments += " --size-constraints POT";
info.Arguments = arguments;
info.CreateNoWindow = false;
UnityEngine.Debug.Log(info.Arguments);
Process.Start(info);
}
}