天天看點

NGUI所見即所得-UIAtlasMaker,UIAtlas

本文的重點就是要将NGUI把多張圖檔打成一個圖集(Atlas)的原理和過程研究下,學習下Unity提供的api和NGUI寫的功能以及設計思想。

UIAtlas

       在介紹UIAtlasMaker之前先看下,先看下UIAtlasMaker的産物——UIAtlas,在建立Sprite的時候都要指定UIAtlas,下面先對UIAtlas的代碼:

C#代碼

NGUI所見即所得-UIAtlasMaker,UIAtlas
NGUI所見即所得-UIAtlasMaker,UIAtlas
NGUI所見即所得-UIAtlasMaker,UIAtlas
  1.        // Material used by this atlas. Name is kept only for backwards compatibility, it used to be public. 
  2. [HideInInspector][SerializeField] Material material; 
  3. // List of all sprites inside the atlas. Name is kept only for backwards compatibility, it used to be public. 
  4. [HideInInspector][SerializeField] List<UISpriteData> mSprites = new List<UISpriteData>(); 
  5. // Size in pixels for the sake of MakePixelPerfect functions. 
  6. [HideInInspector][SerializeField] float mPixelSize = 1f; 
  7. // Replacement atlas can be used to completely bypass this atlas, pulling the data from another one instead. 
  8. [HideInInspector][SerializeField] UIAtlas mReplacement; 
  9.        // Whether the atlas is using a pre-multiplied alpha material. -1 = not checked. 0 = no. 1 = yes. 
  10. 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#代碼

NGUI所見即所得-UIAtlasMaker,UIAtlas
NGUI所見即所得-UIAtlasMaker,UIAtlas
NGUI所見即所得-UIAtlasMaker,UIAtlas
  1. public void MarkAsDirty () 
  2.     { 
  3. #if UNITY_EDITOR 
  4.         UnityEditor.EditorUtility.SetDirty(gameObject); 
  5. #endif 
  6.         if (mReplacement != null) mReplacement.MarkAsDirty(); 
  7.         UISprite[] list = NGUITools.FindActive<UISprite>(); 
  8.         for (int i = 0, imax = list.Length; i < imax; ++i) 
  9.         { 
  10.             UISprite sp = list[i]; 
  11.             if (CheckIfRelated(this, sp.atlas)) 
  12.             { 
  13.                 UIAtlas atl = sp.atlas; 
  14.                 sp.atlas = null; 
  15.                 sp.atlas = atl; 
  16. #if UNITY_EDITOR 
  17.                 UnityEditor.EditorUtility.SetDirty(sp); 
  18. #endif 
  19.             } 
  20.         } 
  21.             …… 
  22.         } 
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#代碼

NGUI所見即所得-UIAtlasMaker,UIAtlas
NGUI所見即所得-UIAtlasMaker,UIAtlas
NGUI所見即所得-UIAtlasMaker,UIAtlas
  1. public class UISpriteData 
  2.     public string name = "Sprite"; 
  3.     public int x = 0; 
  4.     public int y = 0; 
  5.     public int width = 0; 
  6.     public int height = 0; 
  7.     public int borderLeft = 0; 
  8.     public int borderRight = 0; 
  9.     public int borderTop = 0; 
  10.     public int borderBottom = 0; 
  11.     public int paddingLeft = 0; 
  12.     public int paddingRight = 0; 
  13.     public int paddingTop = 0; 
  14.     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區域。

NGUI所見即所得-UIAtlasMaker,UIAtlas

      也就是說,UISpriteData根本沒有實際記錄圖檔的像素資訊,而隻是記錄圖檔在UIAtlas生成的那張大圖的坐标,寬度和高度,以及圖檔拉伸的一些資訊(Border,Padding)。

UIAtlasMaker

      從前面的分析,可以大概知道UIAtlasMaker的原理:把多張圖檔合并成一張大圖,記錄每張圖檔的在生成的大圖的坐标和大小。下面将使用介紹算法那種思路把UIAtlasMakder的工作流程描述清楚。

      在詳細介紹之前,UIAtlasMakder定義了UISpriteEntry這個類:

C#代碼

NGUI所見即所得-UIAtlasMaker,UIAtlas
NGUI所見即所得-UIAtlasMaker,UIAtlas
NGUI所見即所得-UIAtlasMaker,UIAtlas
  1. class SpriteEntry : UISpriteData 
  2.     { 
  3.         // Sprite texture -- original texture or a temporary texture 
  4.         public Texture2D tex; 
  5.         // Whether the texture is temporary and should be deleted 
  6.         public bool temporaryTexture = false; 
  7.     } 
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#代碼

NGUI所見即所得-UIAtlasMaker,UIAtlas
NGUI所見即所得-UIAtlasMaker,UIAtlas
NGUI所見即所得-UIAtlasMaker,UIAtlas
  1. static void ReleaseSprites (List<SpriteEntry> sprites) 
  2.     { 
  3.         foreach (SpriteEntry se in sprites) 
  4.         { 
  5.             if (se.temporaryTexture) 
  6.             { 
  7.                 NGUITools.Destroy(se.tex); 
  8.                 se.tex = null; 
  9.             } 
  10.         } 
  11.         Resources.UnloadUnusedAssets(); 
  12.     } 
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])交流,您的鼓勵和支援是我前進的動力,希望能有更多更好的分享。