图集的打包与加载
开始
图集由来
图集是指将多张较小的图片,拼成一张大图,打包进assetBundle中。好处如下:
1.减少draw call: 多张图片需要多次draw call,合成了一张大图则只需要一次draw call。
2.减少内存占用:OpenGL ES中每张贴图都需要设置成2的n次方才能使用。比如你有一张宽高为100x100和一张宽高为10x10的图片,如果不合成大贴图,那么需要使用128x128和16x16的两张图片(分别是2的7次方和2的4次方),但如果使用一张大图的话,可以把100x100和10x10的图片放到128x128的大图中,这样就用一张图片。
生成图集
使用texturePacker插件生成图集。
原理是,使用texturePacker工具,可将多张小图生成一张整图,并且会生成txt信息,记录了每个小图的信息。 用于之后在加载精灵。
核心代码如下:
//核心代码: 传入四个参数, 依次是commond命令载体, 打成整图后的图片路径, 生成的txt文件位置, 需要被打成图集的图片们(依次用空格间隔开)
string commond = @" --sheet {0} --data {1} --format unity-texture2d --trim-mode None --pack-mode Best --algorithm Polygon --max-size " + AtlasSize + @" --size-constraints POT --disable-rotation --scale 1 {2}";
string arg = string.Format(commond, pngPath, dataPath, atlasPaths.ToString());
EditorUtility.DisplayProgressBar("message","texturePacker",0.1f);
System.Diagnostics.Process process = new System.Diagnostics.Process();
process.StartInfo.Arguments = arg;
process.StartInfo.UseShellExecute = false;
process.StartInfo.FileName = @"TexturePacker.exe";
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.RedirectStandardError = true;
process.Start();
process.WaitForExit();
process.Dispose();
EditorUtility.ClearProgressBar();
AssetDatabase.Refresh();
开启进程调用texturePacker插件,装载多个小图,进行生成。
如下图所示,小图们变成了单张图和一个文本文件
解析图集
生成图集后,还需要对生成的txt文件进行处理,解析成unity中方便处理的数据,这是加载精灵图时必要的信息。如下需要大图以及精灵信息。
Sprite sprite = Sprite.Create(tx2D,data.rect,data.pivot, 100f,0,SpriteMeshType.Tight,Vector4.zero);
精灵数据
精灵数据结构如下:
所以首先建立如下类,与其对应:
public class TUiSpriteData
{
public string spriteName;
public string url;
public Rect rect;
public Vector2 pivot;
public Vector4 border;
}
存储数据的实体
图集这些信息需要缓存下来,所以再增加对应的scriptObject类型文件,此文件可缓存用于缓存数据信息,方便对资源的数据进行处理。
类如下:
public class TUIAtlasSheet : ScriptableObject {
public List<TUiSpriteData> uispriteList = new List<TUiSpriteData>();
}
生成后的实体数据资源文件如下:
prefab进行引用
为了方便管理,再新建prefab去引用到asset文件和png。
核心代码
void AnalySheet(string _atlasName)
{
string atlasPath = $"{atlasResPath}/{_atlasName}";
string outPngPath = $"{atlasResPath}/{_atlasName}/{_atlasName}.png";
string outTxtPath = $"{atlasResPath}/{_atlasName}/{_atlasName}.txt";
string outTextAssetPath = $"{atlasResPath}/{_atlasName}/{_atlasName}.asset";
string outPrefabPath = $"{atlasResPath}/{_atlasName}/{_atlasName}.prefab";
if (Directory.Exists(outPrefabPath))
{
Directory.Delete(outPrefabPath,true);
}
AssetDatabase.Refresh();
GameObject prefab = new GameObject();
Object o = PrefabUtility.CreateEmptyPrefab(BuildTool.RemovePathPrefix(outPrefabPath));
TUIAtlas tAtlas = prefab.AddComponent<TUIAtlas>();
TUIAtlasSheet tSheetList = ScriptableObject.CreateInstance<TUIAtlasSheet>();
AssetDatabase.CreateAsset(tSheetList, BuildTool.RemovePathPrefix( outTextAssetPath));
TextAsset txtAsset = AssetDatabase.LoadAssetAtPath<TextAsset>(BuildTool.RemovePathPrefix( outTxtPath));
Debug.Log("Txt:" + txtAsset);
string txtString = txtAsset.ToString() ;
string[] split = txtString.Split('\n');
foreach (var _curSplit in split)
{
if (_curSplit.Contains(";"))
{
string [] _dataSplit = _curSplit.Split(';');
TUiSpriteData data = new TUiSpriteData();
data.border = Vector4.zero;
data.spriteName = _dataSplit[0];
float x = 0;
float.TryParse(_dataSplit[1],out x);
float y = 0;
float.TryParse(_dataSplit[2],out y);
float width = 0;
float.TryParse(_dataSplit[3],out width);
float height = 0;
float.TryParse(_dataSplit[4],out height);
data.rect = new Rect(x,y,width,height);
data.pivot = new Vector2(0.5f,0.5f);
tSheetList.uispriteList.Add(data);
}
}
SpriteMetaData[] metaData = new SpriteMetaData[tSheetList.uispriteList.Count];
for (int i = 0; i < metaData.Length; i++)
{
metaData[i].pivot = tSheetList.uispriteList[i].pivot;
metaData[i].border = tSheetList.uispriteList[i].border;
metaData[i].rect = tSheetList.uispriteList[i].rect;
metaData[i].name = tSheetList.uispriteList[i].spriteName;
}
TextureImporter textureImporter = TextureImporter.GetAtPath(BuildTool.RemovePathPrefix(outPngPath)) as TextureImporter;
textureImporter.spritesheet = metaData;
textureImporter.textureType = TextureImporterType.Sprite;
textureImporter.spriteImportMode = SpriteImportMode.Multiple;
textureImporter.mipmapEnabled = false;
textureImporter.SaveAndReimport();
tAtlas.sheet = tSheetList;
Texture2D tx2d = AssetDatabase.LoadAssetAtPath<Texture2D>(BuildTool. RemovePathPrefix(outPngPath));
tAtlas.Texture = tx2d;
PrefabUtility.ReplacePrefab(prefab, o);
}
打包图集
打包的思路,
1、 先通过md5算法计算资源的hash值存在本地, 之后每次打包前判断,如果该文件的md5值与之前也存在本地的相同,说明资源没有变化,所以不用打包,否则再进行打包。 由于打包大图集比较耗时,所以这种方法可以跳过不必要的打包,节约时间。
2、进行打包环节后,对prefab和texture进行打包,由于prefab引用了asset数据文件,所以它也会被自动进行打入包中。
md5版本对比
string prefabPath = $"{dirPath}/{atlasName}.prefab";
TCommon.GetNewAssetVersion( prefabPath );
TCommon.GetOldAssetVersion(TCommon._atlas);
if (TCommon.IsNeedPak(prefabPath))
{
Build(dirPath,atlasName);
}
TCommon.SaveAssetVersion(TCommon._atlas);
打包
对prefab与Texture打包
BuildTool.DeleteOutPutDir(TCommon._atlas);
BuildTool.ClearAB();
string prefabPath = $"{inAtlasPath}/{atlasName}/{atlasName}.prefab";
prefabPath = BuildTool.RemovePathPrefix(prefabPath);
string pngPath = $"{inAtlasPath}/{atlasName}/{atlasName}.png";
pngPath = BuildTool.RemovePathPrefix(pngPath);
Debug.Log(BuildTool.RemovePathPrefix(prefabPath));
AssetImporter importerPrefab = AssetImporter.GetAtPath( prefabPath );
importerPrefab.assetBundleName = atlasName;
importerPrefab.assetBundleVariant = BuildTool.ABSUFFIX;
AssetImporter importerPng = AssetImporter.GetAtPath(pngPath);
importerPng.assetBundleName = atlasName;
importerPng.assetBundleVariant = BuildTool.ABSUFFIX;
BuildTool.BuildBundle(outAtlasPath);
BuildTool.ClearAB();
BuildTool.DeleteManifest();
AssetDatabase.Refresh();
加载图集
加载的思路就对应打包的思路,加载出texture与prefab,再获取到prefab得到精灵的数据信息,最后调用Sprite.Create,得到和传入参数。
public Sprite LoadAtlas(string atlasName, string spriteName )
{
Debug.Log("LoadAtlas");
string abPath = $"{Application.streamingAssetsPath}/atlas/{atlasName}{TCommon.ABSUFFIX_AFTER}";
AssetBundle ab = AssetBundle.LoadFromFile(abPath);
GameObject atlasPrefab = ab.LoadAsset<GameObject>(atlasName);
atlasPrefab.SetParent(GameObject.Find("Canvas").transform);
TUIAtlas tuiAtlas = atlasPrefab.GetComponent<TUIAtlas>();
TUIAtlasSheet sheet = tuiAtlas.sheet;
TUiSpriteData data = new TUiSpriteData();
for (int i = 0; i < sheet.uispriteList.Count; i++)
{
if (sheet.uispriteList[i].spriteName == spriteName)
{
data = sheet.uispriteList[i];
break;
}
}
Texture2D tx2D = ab.LoadAsset(atlasName) as Texture2D;
//使用图集和精灵信息,加载出精灵
Sprite sprite = Sprite.Create(tx2D,data.rect,data.pivot, 100f,0,SpriteMeshType.Tight,Vector4.zero);
return sprite;
}