NGUI所見即所得之UIWidget , UIGeometry & UIDrawCall
UIWidget是所有UI組件的抽象基類,作為基類當(dāng)然定義了必須的成員變量和函數(shù),接觸過MFC或其他UI組件開發(fā),想必都知道有一堆參數(shù)設(shè)置,尤其是Visual Studio的可視化界面,簡(jiǎn)直太豐富了,UIWidget要當(dāng)UI組件的爹就必須得具備這些,下面就一一介紹:
Pivot
Pivot,這個(gè)枚舉,其實(shí)定義了GameObject中心坐標(biāo)在整個(gè)組件的位置,這個(gè)跟UIStretch很類似,只不過UIStretch說的是組件相對(duì)于屏幕的位置。
C#代碼 public enum Pivot      {          TopLeft,          Top,          ToPRight,          Left,          Center,          Right,          BottomLeft,          Bottom,          BottomRight,      }
public enum Pivot      {          TopLeft,          Top,          ToPRight,          Left,          Center,          Right,          BottomLeft,          Bottom,          BottomRight,      }  Pivot可以提供開發(fā)者更多的定位模式,可以方便實(shí)現(xiàn)組件對(duì)齊,如對(duì)個(gè)UILabel組件的文本居中對(duì)齊。當(dāng)然這樣,在計(jì)算組件的四個(gè)角的頂點(diǎn)坐標(biāo)(localCorners)就得考慮Pivot。
localCorners,worldCorners & innerWorldCorners
這三個(gè)變量都是四個(gè)角的頂點(diǎn)坐標(biāo),只是localCorners是計(jì)算局部的(相對(duì)gameObject的中心而言)坐標(biāo),worldCorners只是將localCorners作為世界坐標(biāo)空間的坐標(biāo),innerWorlCorners則考慮了邊框Border。
C#代碼 public virtual Vector3[] localCorners  {      get      {          Vector2 offset = pivotOffset;            float x0 = -offset.x * mWidth;          float y0 = -offset.y * mHeight;          float x1 = x0 + mWidth;          float y1 = y0 + mHeight;            mCorners[0] = new Vector3(x0, y0, 0f);          mCorners[1] = new Vector3(x0, y1, 0f);          mCorners[2] = new Vector3(x1, y1, 0f);          mCorners[3] = new Vector3(x1, y0, 0f);            return mCorners;      }  }
public virtual Vector3[] localCorners  {      get      {          Vector2 offset = pivotOffset;            float x0 = -offset.x * mWidth;          float y0 = -offset.y * mHeight;          float x1 = x0 + mWidth;          float y1 = y0 + mHeight;            mCorners[0] = new Vector3(x0, y0, 0f);          mCorners[1] = new Vector3(x0, y1, 0f);          mCorners[2] = new Vector3(x1, y1, 0f);          mCorners[3] = new Vector3(x1, y0, 0f);            return mCorners;      }  }  上面代碼中的pivotOffset的計(jì)算就考慮了Pivot:
C#代碼 static public Vector2 GetPivotOffset (UIWidget.Pivot pv)      {          Vector2 v = Vector2.zero;            if (pv == UIWidget.Pivot.Top || pv == UIWidget.Pivot.Center || pv == UIWidget.Pivot.Bottom) v.x = 0.5f;          else if (pv == UIWidget.Pivot.TopRight || pv == UIWidget.Pivot.Right || pv == UIWidget.Pivot.BottomRight) v.x = 1f;          else v.x = 0f;            if (pv == UIWidget.Pivot.Left || pv == UIWidget.Pivot.Center || pv == UIWidget.Pivot.Right) v.y = 0.5f;          else if (pv == UIWidget.Pivot.TopLeft || pv == UIWidget.Pivot.Top || pv == UIWidget.Pivot.TopRight) v.y = 1f;          else v.y = 0f;            return v;      }
static public Vector2 GetPivotOffset (UIWidget.Pivot pv)      {          Vector2 v = Vector2.zero;            if (pv == UIWidget.Pivot.Top || pv == UIWidget.Pivot.Center || pv == UIWidget.Pivot.Bottom) v.x = 0.5f;          else if (pv == UIWidget.Pivot.TopRight || pv == UIWidget.Pivot.Right || pv == UIWidget.Pivot.BottomRight) v.x = 1f;          else v.x = 0f;            if (pv == UIWidget.Pivot.Left || pv == UIWidget.Pivot.Center || pv == UIWidget.Pivot.Right) v.y = 0.5f;          else if (pv == UIWidget.Pivot.TopLeft || pv == UIWidget.Pivot.Top || pv == UIWidget.Pivot.TopRight) v.y = 1f;          else v.y = 0f;            return v;      }  發(fā)現(xiàn)pivotOffset是一個(gè)由0,0.5,1組成的二維向量,所以前面計(jì)算localCorners的原理就可想而知了。
當(dāng)然還有諸如width(minWidth),height(minHeight),depth(raycastDepth),alpha(finalAlpha),看了代碼就自然一目了然了。rayCastDepth和finalAlpha都考慮了Uipanel的因素,所以有時(shí)表層看著沒問題,可能就要去看下底層的實(shí)現(xiàn)。
兩大基石:UIDrawCall和UIGeometry
根據(jù)我有限的3D知識(shí)(其實(shí)沒有學(xué)過),隱約覺得3D呈現(xiàn)出來的東西都是由頂點(diǎn)(vertice)構(gòu)成的Mesh通過紋理貼圖構(gòu)成的材質(zhì)渲染出來的。
但是NGUI所有組件都沒有看到Mesh Render,只有transform信息和腳本,查看UIWidget:
C#代碼 /// <summary>  /// Internal usage -- draw call that's drawing the widget.  /// </summary>    public UIDrawCall drawCall { get; set; }    // Widget's generated geometry  UIGeometry mGeom = new UIGeometry();
        /// <summary>  /// Internal usage -- draw call that's drawing the widget.  /// </summary>    public UIDrawCall drawCall { get; set; }    // Widget's generated geometry  UIGeometry mGeom = new UIGeometry();  這就是UIDrawCall和UIGeometry神一般的存在。
UIGeometry
UIGeometry其實(shí)是UI組件的數(shù)據(jù)倉(cāng)庫(kù),存儲(chǔ)UI組件的Vertices,UVs和Colors,以及相對(duì)UIPanel的頂點(diǎn)Vertices,法向量和切線:
C#代碼 /// <summary>  /// Widget's vertices (before they get transformed).  /// </summary>    public BetterList<Vector3> verts = new BetterList<Vector3>();    /// <summary>  /// Widget's texture coordinates for the geometry's vertices.  /// </summary>    public BetterList<Vector2> uvs = new BetterList<Vector2>();    /// <summary>  /// Array of colors for the geometry's vertices.  /// </summary>    public BetterList<Color32> cols = new BetterList<Color32>();    // Relative-to-panel vertices, normal, and tangent  BetterList<Vector3> mRtpVerts = new BetterList<Vector3>();  Vector3 mRtpNormal;  Vector4 mRtpTan;
/// <summary>  /// Widget's vertices (before they get transformed).  /// </summary>    public BetterList<Vector3> verts = new BetterList<Vector3>();    /// <summary>  /// Widget's texture coordinates for the geometry's vertices.  /// </summary>    public BetterList<Vector2> uvs = new BetterList<Vector2>();    /// <summary>  /// Array of colors for the geometry's vertices.  /// </summary>    public BetterList<Color32> cols = new BetterList<Color32>();    // Relative-to-panel vertices, normal, and tangent  BetterList<Vector3> mRtpVerts = new BetterList<Vector3>();  Vector3 mRtpNormal;  Vector4 mRtpTan;  UIGeometry為此提供了三個(gè)函數(shù),也可以說是為渲染繪制做的三個(gè)步驟:
Step 1: Clear() 情況存儲(chǔ)的信息
Step 2: ApplyTransform() 將verts轉(zhuǎn)化為相對(duì)UIPanel的頂點(diǎn)坐標(biāo)mRtpVerts
Step 3: WriteToBuffers() 將數(shù)據(jù)倉(cāng)庫(kù)緩存的數(shù)據(jù)添加到UIPanel的緩存數(shù)據(jù)隊(duì)列中去。
UIWidget分別用UpdateGeometry和WriteToBuffers對(duì)UIGeometry的ApplyTransform和WriteToBuffers進(jìn)行封裝調(diào)用,ApplyTransform是根據(jù)UIPanel的坐標(biāo)調(diào)整Vertices的坐標(biāo),WriteToBuffers將UIGeometry的Vertices,UVs和Colors添加進(jìn)UIPanel的Vertices,UVs和Colors的BetterList中。
這里還有一個(gè)細(xì)節(jié)就是:UIGeometry中的Vertices,UVs和Colors的BetterList的buffer什么時(shí)候得到,因?yàn)檫@些都是UIWidget或其子類的信息,所以在UIWidget的子類UILabel,UISprite和UITexture中OnFill函數(shù)生成UIGeometry的BetterList的buffer。
UIDrawCall
如果是UIGeometry為了渲染繪制準(zhǔn)備數(shù)據(jù),那么UIDrawCall其實(shí)是定義了渲染繪制需要的基本組件。這里拿煮菜做個(gè)比喻幫助理解:UIGeometry好比為煮菜準(zhǔn)備食材,UIDrawCall好比是煮菜的工具(鍋,爐子等),UIPanel就是大廚了決定著什么時(shí)候該煮菜,UIWidget(UILabel,UISprite和UITexture)是這道菜怎么樣的最終呈現(xiàn)。會(huì)不會(huì)很好理解呢?
下面就直接看下Set函數(shù):
C#代碼 if (mFilter == null) mFilter = gameObject.AddComponent<MeshFilter>();  if (mRen == null) mRen = gameObject.GetComponent<MeshRenderer>();
if (mFilter == null) mFilter = gameObject.AddComponent<MeshFilter>();  if (mRen == null) mRen = gameObject.GetComponent<MeshRenderer>();  在Set函數(shù)中就給gameObjdect添加了MeshFilter和MeshRenderer組件,當(dāng)然還做了其他輔助的工作。
此外,在UpdateMaterials驚奇的發(fā)現(xiàn)了UIPanel clipView的AlphaClip和SoftClip的實(shí)現(xiàn),其實(shí)就是更換了Material的Shader,這也體現(xiàn)了Shader強(qiáng)大,不得不學(xué)呀,DSQiu在后面也會(huì)對(duì)Unity的Shader編程做一個(gè)整理。
回放細(xì)節(jié),回笼鍛造
看了上面就會(huì)覺得寫得的確是所見即所得——D.S.Qiu好像都沒有看到什么要害,都說學(xué)武功就要學(xué)習(xí)上次心法,一點(diǎn)皮毛總是會(huì)惹來恥笑。
還是回到UIWidget這個(gè)腳本中的兩個(gè)函數(shù):WriteToBuffers,OnFill,UpdateGeometry。
WriteToBuffers和OnFill這兩個(gè)函數(shù)都是將Vertices,UVs和Colors等add進(jìn)參數(shù)的List中去,查看WriteToBuffers的調(diào)用出發(fā)現(xiàn)其參數(shù)是UIPanel的Vertices,UVs和Colors,而OnFill的參數(shù)是UIGeometry的Vertices,UVs和Colors。
WriteToBuffers只是對(duì)UIGeometry的封裝調(diào)用,也就是說將UIGeometry的Vertices,UVs和Colors等信息add進(jìn)UIPanel的對(duì)應(yīng)List中。
在看UIGeometry的腳本,一直有一個(gè)疑問:UIGeometry的Vertices,UVs和Colors的List沒有看到add方法的執(zhí)行,只有在WriteToBuffers被add。直到看到了OnFill才恍然大悟,雖然UIWidget的OnFill是虛函數(shù),沒有具體實(shí)現(xiàn),看了下UISprite重寫的OnFill函數(shù),就是把Vertices,UVs和Colors 添加到UIGeometry的Vertices,UVs和Colors中。
轉(zhuǎn)了一大圈,發(fā)現(xiàn)最后所有UI組件的Vertices,UVs和Colors都匯集到UIPanel的Vertices,UVs和Colors去了,然后UIPanel指定給UIDrawCall渲染就行了,具體細(xì)節(jié)還要等攻克UIPanel就明白了。
其他細(xì)節(jié)
到這里差不多把本文的內(nèi)容都掏干凈了,剩下的就是看下UIWidget的一些實(shí)現(xiàn)細(xì)節(jié),MakePixelPerfect():對(duì)gameObject的localPosition和locaScale進(jìn)行微調(diào)和糾正。
SetDirty():調(diào)用UIPanel的SetDirty()對(duì)組件的變更進(jìn)行重建(rebuilt)。
CreatPanel():這個(gè)函數(shù)可以得知,所有UI組件一定是放在UIPanel的子節(jié)點(diǎn)上的,從前面的分析也可以知道:因?yàn)槭荱IPanel對(duì)統(tǒng)一對(duì)組件進(jìn)行渲染繪制的,所以如果沒有UIPanel有點(diǎn)“皮之不存,毛將附焉”的意思。
這也越發(fā)激發(fā)D.S.Qiu 去啃UIPanel這個(gè)主心骨。
小結(jié):
總算對(duì)UIWidget,UIGeomerty 和UIDrawCall有了一個(gè)清晰的認(rèn)識(shí),這三個(gè)組件可以說是NGUI的背后無名英雄(平時(shí)使用都不會(huì)接觸者三個(gè)腳本),功能很強(qiáng)大但很簡(jiǎn)單,真的很佩服NGUI的設(shè)計(jì)思路。
但我也有點(diǎn)小微詞:UIWidget都被定義成長(zhǎng)方形的,要是能提供自定義UI的接口(如我想畫一個(gè)圓形UI),那將會(huì)更強(qiáng)大;底層緩存了很多數(shù)據(jù),這個(gè)是內(nèi)存和開發(fā)者的代碼邏輯也是要考慮到的。
最后附上①畫的NGUI框架圖:

如果您對(duì)D.S.Qiu有任何建議或意見可以在文章后面評(píng)論,或者發(fā)郵件(gd.s.qiu@Gmail.com)交流,您的鼓勵(lì)和支持是我前進(jìn)的動(dòng)力,希望能有更多更好的分享。
轉(zhuǎn)載請(qǐng)?jiān)?strong>文首注明出處:http://dsqiu.iteye.com/blog/1965340
更多精彩請(qǐng)關(guān)注D.S.Qiu的博客和微博(ID:靜水逐風(fēng))
參考:
①dujimache:http://www.unitymanual.com/forum.php?mod=viewthread&tid=5579&highlight=NGUI%E6%A1%86%E6%9E%B6
新聞熱點(diǎn)
疑難解答
圖片精選
網(wǎng)友關(guān)注