(前面是廢話,可以略過,分割線下面是內容)
前兩天研究完了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); } }