本文的重點就是要将NGUI把多張圖檔打成一個圖集(Atlas)的原理和過程研究下,學習下Unity提供的api和NGUI寫的功能以及設計思想。
UIAtlas
在介紹UIAtlasMaker之前先看下,先看下UIAtlasMaker的産物——UIAtlas,在建立Sprite的時候都要指定UIAtlas,下面先對UIAtlas的代碼:
C#代碼

- // Material used by this atlas. Name is kept only for backwards compatibility, it used to be public.
- [HideInInspector][SerializeField] Material material;
- // List of all sprites inside the atlas. Name is kept only for backwards compatibility, it used to be public.
- [HideInInspector][SerializeField] List<UISpriteData> mSprites = new List<UISpriteData>();
- // Size in pixels for the sake of MakePixelPerfect functions.
- [HideInInspector][SerializeField] float mPixelSize = 1f;
- // Replacement atlas can be used to completely bypass this atlas, pulling the data from another one instead.
- [HideInInspector][SerializeField] UIAtlas mReplacement;
- // Whether the atlas is using a pre-multiplied alpha material. -1 = not checked. 0 = no. 1 = yes.
- int mPMA = -1;
// Material used by this atlas. Name is kept only for backwards compatibility, it used to be public.
[HideInInspector][SerializeField] Material material;
// List of all sprites inside the atlas. Name is kept only for backwards compatibility, it used to be public.
[HideInInspector][SerializeField] List<UISpriteData> mSprites = new List<UISpriteData>();
// Size in pixels for the sake of MakePixelPerfect functions.
[HideInInspector][SerializeField] float mPixelSize = 1f;
// Replacement atlas can be used to completely bypass this atlas, pulling the data from another one instead.
[HideInInspector][SerializeField] UIAtlas mReplacement;
// Whether the atlas is using a pre-multiplied alpha material. -1 = not checked. 0 = no. 1 = yes.
int mPMA = -1;
可以很驚奇的發現UIAtlas的成員變量竟然極其精簡,就隻有:Material material; UISpriteDatat的List mSprites; 每個像素的大小 mPixelSize 這個跟UIWidget的像素大小應該是類似的, UIAtlas mReplacement; 這個目前隻能望文生義,雖然知道是用來替換UIAtlas,但好像沒有發現哪裡會有用到,在UISprite的那裡隻是選擇不同的UIAtlas而不是替換UIAtlas本身,就暫且擱置, 最後一個是mPMA這個是pre-multiplied的像素存儲技術,大緻了解下,好像是把alpha的值存儲在每個顔色的RGB中去已達到節省存儲空間的技術,想要了解更多可以自行google下。
接着看UIAtlas,接下來就是把屬性的{get,set} 以及幾個函數,也很簡單,主要就是屬性改變,就調用MakeAsDirty()函數,下面看下這個函數的前面一部分:
C#代碼

- public void MarkAsDirty ()
- {
- #if UNITY_EDITOR
- UnityEditor.EditorUtility.SetDirty(gameObject);
- #endif
- if (mReplacement != null) mReplacement.MarkAsDirty();
- UISprite[] list = NGUITools.FindActive<UISprite>();
- for (int i = 0, imax = list.Length; i < imax; ++i)
- {
- UISprite sp = list[i];
- if (CheckIfRelated(this, sp.atlas))
- {
- UIAtlas atl = sp.atlas;
- sp.atlas = null;
- sp.atlas = atl;
- #if UNITY_EDITOR
- UnityEditor.EditorUtility.SetDirty(sp);
- #endif
- }
- }
- ……
- }
public void MarkAsDirty ()
{
#if UNITY_EDITOR
UnityEditor.EditorUtility.SetDirty(gameObject);
#endif
if (mReplacement != null) mReplacement.MarkAsDirty();
UISprite[] list = NGUITools.FindActive<UISprite>();
for (int i = 0, imax = list.Length; i < imax; ++i)
{
UISprite sp = list[i];
if (CheckIfRelated(this, sp.atlas))
{
UIAtlas atl = sp.atlas;
sp.atlas = null;
sp.atlas = atl;
#if UNITY_EDITOR
UnityEditor.EditorUtility.SetDirty(sp);
#endif
}
}
……
}
可以看出這個函數其實就是更新以replacemet和this為UIAtlas的UISprite的UIAtlas,就是這麼簡單。
UISpriteData
在UIAtlas中有GetSprite()和GetListOfSprites(),其中GetSprite傳回的UISpriteData,讓人不得不猜想UISpriteData就是UISprite中的圖檔,但其實它隻是一個結構體:
C#代碼

- public class UISpriteData
- {
- public string name = "Sprite";
- public int x = 0;
- public int y = 0;
- public int width = 0;
- public int height = 0;
- public int borderLeft = 0;
- public int borderRight = 0;
- public int borderTop = 0;
- public int borderBottom = 0;
- public int paddingLeft = 0;
- public int paddingRight = 0;
- public int paddingTop = 0;
- public int paddingBottom = 0;
public class UISpriteData
{
public string name = "Sprite";
public int x = 0;
public int y = 0;
public int width = 0;
public int height = 0;
public int borderLeft = 0;
public int borderRight = 0;
public int borderTop = 0;
public int borderBottom = 0;
public int paddingLeft = 0;
public int paddingRight = 0;
public int paddingTop = 0;
public int paddingBottom = 0;
一看這些成員變量,就會感覺很熟悉,這不是就是UI元件的基本熟悉麼,可以進一步看下UIInspectorEditor界面,就更加确信了,而且Border其實用在SliceSprite的,至于Padding就是空白不顯示區域,而且x,y,width,height其實該圖檔就是在UIAtlas上Rect區域。
也就是說,UISpriteData根本沒有實際記錄圖檔的像素資訊,而隻是記錄圖檔在UIAtlas生成的那張大圖的坐标,寬度和高度,以及圖檔拉伸的一些資訊(Border,Padding)。
UIAtlasMaker
從前面的分析,可以大概知道UIAtlasMaker的原理:把多張圖檔合并成一張大圖,記錄每張圖檔的在生成的大圖的坐标和大小。下面将使用介紹算法那種思路把UIAtlasMakder的工作流程描述清楚。
在詳細介紹之前,UIAtlasMakder定義了UISpriteEntry這個類:
C#代碼

- class SpriteEntry : UISpriteData
- {
- // Sprite texture -- original texture or a temporary texture
- public Texture2D tex;
- // Whether the texture is temporary and should be deleted
- public bool temporaryTexture = false;
- }
class SpriteEntry : UISpriteData
{
// Sprite texture -- original texture or a temporary texture
public Texture2D tex;
// Whether the texture is temporary and should be deleted
public bool temporaryTexture = false;
}
隻是在UISpriteData基礎上擴充了一個Texture2D tex的成員變量,那麼這個類到底做什麼用呢?通過分析代碼可以知道:UISpriteEntry是UISpriteData和Texture2D的中間體,是Texture2D和UISpriteData轉化的載體,就提過程下面會有引人。
UIAtlasMaker的工作流程:
1)選擇圖檔or圖集,函數:void OnSelectAtlas (Object obj) 和 List<Texture> GetSelectedTextures ()
2)導入選擇圖檔的Texture2D,函數:static List<Texture2D> LoadTextures (List<Texture> textures)
3)将Texture2D轉出UISpriteEntry,函數:static List<SpriteEntry> CreateSprites (List<Texture> textures) ,這個函數中包括了2)的步驟
3)打包成大圖檔并生成UISpriteData,函數:static bool PackTextures (Texture2D tex, List<SpriteEntry> sprites),這個函數提供兩種打包方式:1.NGUI自己實作類UITextPacker,2.Unity3D提供的api
4)釋放UISpriteEntry,函數:static void ReleaseSprites (List<SpriteEntry> sprites),主要是通過NGUITools.Destroy銷毀紋理,最後調用Resources.UnloadUnusedAssets();,這個趁機告訴我們Unity中資源銷毀的方法,一個不能少哈。
C#代碼

- static void ReleaseSprites (List<SpriteEntry> sprites)
- {
- foreach (SpriteEntry se in sprites)
- {
- if (se.temporaryTexture)
- {
- NGUITools.Destroy(se.tex);
- se.tex = null;
- }
- }
- Resources.UnloadUnusedAssets();
- }
static void ReleaseSprites (List<SpriteEntry> sprites)
{
foreach (SpriteEntry se in sprites)
{
if (se.temporaryTexture)
{
NGUITools.Destroy(se.tex);
se.tex = null;
}
}
Resources.UnloadUnusedAssets();
}
差不多就是這樣子,其他就是一些功能操作:add,replace,delete,udpate,extract。
這樣看來,把UIAtlas分析清楚,掌握UIAtlasMaker的原理應該不難,當然裡面有很多細節,使用很多api:AssetDatabase,Texture,SelectedObject以及NGUI自己封裝的一些函數。
小結:
UIAtlas雖然在使用NGUI過程中用的太多了,但是經過上面的分析,其實不是很難,個人覺得還沒有UIRoot 的難以駕馭,即使UIRoot很短很單一。這裡說下D.S.Qiu的關于快速學習的一點體會:古人有雲,“工欲善其事,必先利其器”,NGUI最為一個工具,要使用好它,就要充分掌握它, 換句話說就要懂它,當然還可以從中偷到很多師——功能的具體實作和代碼邏輯的設計,其實一直覺得我對細節都不求甚解,會用就可以走路了,又不用自己寫一個NGUI,當然細節都能掌握就更完美了。已經到淩晨一點了,先寫這麼多,有補充在加。
如果您對D.S.Qiu有任何建議或意見可以在文章後面評論,或者發郵件([email protected])交流,您的鼓勵和支援是我前進的動力,希望能有更多更好的分享。