NGUI所見即所得之UISPRite & UILabel
UISprite UILabel是NGUI最基礎的組件,是UIWidget的子類,之前寫過NGUI所見即所得之UIWidget , UIGeometry & UIDrawCall UIWidget,UIGeometry & UIDrawCall是NGUI的UI組件繪制的底層實現,UISprite,UILabel就把要繪制的材料——頂點,紋理,紋理UV,顏色值等傳給底層,底層負責協調繪制渲染。
UISprite
NGUI3.0.3d版本已經將UISprite,UIFillSprite,UISliceSprite,UITiledSprite整合在一個UISprite中,用Type來區分:
C#代碼
public enum Type { Simple, Sliced, Tiled, Filled, } UISprite主要是重寫(override)OnFill函數——把Vertices,UVs和Colors添加進UIGeometry中。在分析OnFill之前,先看下drawingDimensions的實現:
C#代碼
/// <summary> /// Sprite's dimensions used for drawing. X = left, Y = bottom, Z = right, W = top. /// This function automatically adds 1 pixel on the edge if the sprite's dimensions are not even. /// It's used to achieve pixel-perfect sprites even when an odd dimension sprite happens to be centered. /// </summary> Vector4 drawingDimensions { get { if (mSprite == null) { return new Vector4(0f, 0f, mWidth, mHeight); } int padLeft = mSprite.paddingLeft; int padBottom = mSprite.paddingBottom; int padRight = mSprite.paddingRight; int padTop = mSprite.paddingTop; Vector2 pv = pivotOffset; int w = mSprite.width + mSprite.paddingLeft + mSprite.paddingRight; int h = mSprite.height + mSprite.paddingBottom + mSprite.paddingTop; if ((w & 1) == 1) ++padRight; if ((h & 1) == 1) ++padTop; float invW = 1f / w; float invH = 1f / h; Vector4 v = new Vector4(padLeft * invW, padBottom * invH, (w - padRight) * invW, (h - padTop) * invH); v.x -= pv.x; v.y -= pv.y; v.z -= pv.x; v.w -= pv.y; v.x *= mWidth; v.y *= mHeight; v.z *= mWidth; v.w *= mHeight; return v; } } 其實drawingDimensions可以看成紋理貼圖的x,y軸的區間,也就是說紋理在x軸區間為(v.x, v.z),y軸區間為(v.y,v.w)的矩形。注釋中,說道如果drawingDimension不是偶數,則會添加一個像素,這個處理導致UISprite的類型是Tiled,有可能出現間隙,網上也有人通過設置一個像素的Border來解決這個問題,當然最本質就是紋理貼圖像素是偶數的。
下面給出OnFill函數,在代碼中給出注釋:
C#代碼
public override void OnFill (BetterList<Vector3> verts, BetterList<Vector2> uvs, BetterList<Color32> cols) { Texture tex = mainTexture; if (tex != null) { if (mSprite == null) mSprite = atlas.GetSprite(spriteName); if (mSprite == null) return; mOuterUV.Set(mSprite.x, mSprite.y, mSprite.width, mSprite.height); mInnerUV.Set(mSprite.x + mSprite.borderLeft, mSprite.y + mSprite.borderTop, mSprite.width - mSprite.borderLeft - mSprite.borderRight, mSprite.height - mSprite.borderBottom - mSprite.borderTop); mOuterUV = NGUIMath.ConvertToTexCoords(mOuterUV, tex.width, tex.height); // Convert from top-left based pixel coordinates to bottom-left based UV coordinates. mInnerUV = NGUIMath.ConvertToTexCoords(mInnerUV, tex.width, tex.height); } switch (type) { case Type.Simple: SimpleFill(verts, uvs, cols); break; case Type.Sliced: SlicedFill(verts, uvs, cols); break; case Type.Filled: FilledFill(verts, uvs, cols); break; case Type.Tiled: TiledFill(verts, uvs, cols); break; } } SimpleFill最簡單了,直接添加Verts,UVs,Colors,SimpleFill就只有四個頂點繪制貼圖,SliceFill,TiledFill,FiledFill都是轉換為SimpleFill的情況來繪制的。
C#代碼
protected void SimpleFill (BetterList<Vector3> verts, BetterList<Vector2> uvs, BetterList<Color32> cols) { Vector2 uv0 = new Vector2(mOuterUV.xMin, mOuterUV.yMin); Vector2 uv1 = new Vector2(mOuterUV.xMax, mOuterUV.yMax); Vector4 v = drawingDimensions; verts.Add(new Vector3(v.x, v.y)); verts.Add(new Vector3(v.x, v.w)); verts.Add(new Vector3(v.z, v.w)); verts.Add(new Vector3(v.z, v.y)); uvs.Add(uv0); uvs.Add(new Vector2(uv0.x, uv1.y)); uvs.Add(uv1); uvs.Add(new Vector2(uv1.x, uv0.y)); Color colF = color; colF.a *= mPanel.alpha; Color32 col = atlas.premultipliedAlpha ? NGUITools.ApplyPMA(colF) : colF; cols.Add(col); cols.Add(col); cols.Add(col); cols.Add(col); } SliceFill其實就是把矩形紋理切成九宮格,就是九個SimpleFill,貼出注釋,詳細看源碼:
Sliced sprite fill function is more complicated as it generates 9 quads instead of 1.
TiledFill可以簡單的看成:(組件面積/紋理貼圖面積 )個SimpleFill
FilledFill代碼是很復雜,但是其實也是把扇形切割成四邊形來SimpleFill
這種解決問題的方法很值得借鑒,記得高中的時候解數學難題一般都是將復雜問題分解成多個簡單的問題,SimpleFill相當于是一個子問題,Slice,Tiled,Filed都是轉化為Simple的情況來解決,這個點推到動態規劃算法的方程一樣,如果對問題理解好了,自然就迎刃而解。
UILabel
UILabel的樣式越來越多了,和UISprite一樣一個腳本充當多種角色(UISprite,UIFilledSprite,UISlicedSprite),由于UIFont有使用ttf 的Font的動態字體,還有使用BMFont等軟件編輯生成的Bitmap字體,所以成員變量比較多。mFont就是當前UILabel使用的字體,如果是動態字體的話,trueTypeFont(ttf的縮寫)就是Font,字體的大小就是mFont的defaultSize。
supportEncoding: 是否支持顏色和換行
大多數屬性改變都會調用到ProcessText():
C#代碼
/// <summary> /// Process the raw text, called when something changes. /// </summary> void ProcessText (bool legacyMode) { if (!isValid) return; mChanged = true; hasChanged = false; int fs = fontSize; //字體大小 float ps = pixelSize; //UIFont的像素大小 float invSize = 1f / ps; mPrintedSize = Mathf.Abs(legacyMode ? Mathf.RoundToInt(cachedTransform.localScale.x) : fs); float lw = legacyMode ? (mMaxLineWidth != 0 ? mMaxLineWidth * invSize : 1000000) : width * invSize; float lh = legacyMode ? (mMaxLineHeight != 0 ? mMaxLineHeight * invSize : 1000000) : height * invSize; if (mPrintedSize > 0) { for (;;) { mScale = (float)mPrintedSize / fs; //計算出放縮比 bool fits = true; int pw = (mOverflow == Overflow.ResizeFreely) ? 100000 : Mathf.RoundToInt(lw / mScale); int ph = (mOverflow == Overflow.ResizeFreely || mOverflow == Overflow.ResizeHeight) ? 100000 : Mathf.RoundToInt(lh / mScale); if (lw > 0f || lh > 0f) { if (mFont != null) fits = mFont.WrapText(mText, fs, out mProcessedText, pw, ph, mMaxLineCount, mEncoding, mSymbols); #if DYNAMIC_FONT else fits = NGUIText.WrapText(mText, mTrueTypeFont, fs, mFontStyle, pw, ph, mMaxLineCount, mEncoding, out mProcessedText); #endif } else mProcessedText = mText; // Remember the final printed size if (!string.IsNullOrEmpty(mProcessedText)) { if (mFont != null) mCalculatedSize = mFont.CalculatePrintedSize(mProcessedText, fs, mEncoding, mSymbols); #if DYNAMIC_FONT else mCalculatedSize = NGUIText.CalculatePrintedSize(mProcessedText, mTrueTypeFont, fs, mFontStyle, mEncoding); #endif } else mCalculatedSize = Vector2.zero; //根據不同overflowMethod調整文字顯示 if (mOverflow == Overflow.ResizeFreely) { mWidth = Mathf.RoundToInt(mCalculatedSize.x * ps); mHeight = Mathf.RoundToInt(mCalculatedSize.y * ps); } else if (mOverflow == Overflow.ResizeHeight) { mHeight = Mathf.RoundToInt(mCalculatedSize.y * ps); } else if (mOverflow == Overflow.ShrinkContent && !fits) { if (--mPrintedSize > 1) continue; } // Upgrade to the new system if (legacyMode) { width = Mathf.RoundToInt(mCalculatedSize.x * ps); height = Mathf.RoundToInt(mCalculatedSize.y * ps); cachedTransform.localScale = Vector3.one; } break; } } else { cachedTransform.localScale = Vector3.one; mProcessedText = ""; mScale = 1f; } } 這里涉及的變量比較多,做下簡單的列舉:
C#代碼
int fs = fontSize; //字體大小 float ps = pixelSize; //一個像素的大小 float invSize = 1f/ps; 可以認為unity單位1的距離有多少個像素 mPritedSize; 可以認為等同 fs,即字體大小 float lw 和 float lh; //長和寬各有多少個像素 mScale; 就是transfrom的放縮比 int pw 和 int ph; //在不同overflowMethod下的像素個數,考慮放縮mScale mCalculatedSize; //文字一共占用多少個像素面積 mWidth = mCalculatedSize * ps; 兩個函數: mFont.WrapText; //根據當前參數調整文字顯示 mCalculatedSize; //計算當前顯示的文字的像素面積 ProcessText的目的就是通過當前設置的參數去調整文字的顯示以及長寬等……
UIWidget的子類都有一個必可少的函數OnFill,因為整個函數是虛函數,函數的作用在前面都以及講過,這里主要是調用UIFont的Print函數對Verts,UVs,Colors進行填充,就不貼代碼了,有需求可以仔細研究下……
『文本縮放樣式:
C#代碼
public enum Overflow { ShrinkContent, ClampContent, ResizeFreely, ResizeHeight, } public enum Crispness { Never, OnDesktop, Always, } UILabel現在支持四種文本樣式,這里做下簡單的解釋:
1.ShrinkContent,總是顯示所有文字,根據當前的width和height進行縮放
2.ClampContent,一看到Clamp就想起Clamp函數,也就是不管文本多少個字,根據當前的width和height來顯示,超出部分不顯示
3.ResizeFreely,會對當前的文字長度和行數來調整,UILabel的width和height
4.ResizeHeight,如果UILabel的width不夠就會調整height值,進行多行顯示,即保持寬度不變,更加文本長度調整height
除了第一種會對文字的大小進行放縮,其他三個樣式都不會對文字本身的大小進行調整。』
增補于 2013,12,23 下午 16:38
『Bug修復:
更新了NGUI到版本3.0.7f3,發現使用動態字體時,多行文字總是向上增長(而且只有動態字體才出現,Bitmap沒有這個問題,后面測試了才發現的),然后只有比對UILabel和NGUIText的代碼:
C#代碼
v0.x = x + mTempChar.vert.xMin; v0.y = y + mTempChar.vert.yMax - baseline; 只要細致的,都能看出有問題,x和y都是絕對值(正),所以如果x方向是 + ,那么 y方向就應該是 -。
然后又去看了之前的版本發現:
C#代碼
v0.x = (x + mTempChar.vert.xMin); r.vert.yMax + baseline); 看到這里就應該恍然大悟了,NGUI的技術太不認真了哈。
這樣也推薦下文件比較工具Beyound Compare軟件,還是很容易兩個文件不同的地方。』
最近沒有時間寫博客了,只有利用寫時間進行修修補補 增補于:2014,1,3:14:00
小結:
其實,UISprite很簡單,不難,只是之前一直對Filed ,Slice,Tiled的實現很好奇,感覺很神奇,當然D.S.Qiu還有很多沒有弄明白的:
1.UVs是的作用是?
2.在哪里指定材質Material或紋理
3.Unity單位和屏幕分辨率以及像素大小的關系,現在感覺挺混亂的。
還是盡早去琢磨UIPanel吧。
如果您對D.S.Qiu有任何建議或意見可以在文章后面評論,或者發郵件(gd.s.qiu@Gmail.com)交流,您的鼓勵和支持是我前進的動力,希望能有更多更好的分享。
轉載請在文首注明出處:http://dsqiu.iteye.com/blog/1968270
更多精彩請關注D.S.Qiu的博客和微博(ID:靜水逐風)
本來按到原來本來想另起一篇寫UILabel的,但是感覺目前的寫不到很長和耐人尋味的篇幅,所以只有接在這篇寫下去,改了個題目。
然后上面的疑問,也有了點理解:1.Verts是渲染的位置的頂點,而且是相對于UIPanel,UVs就是紋理上的坐標,如果說Verts是畫板,UVs就是顏色板上的顏色;2.Material就是UIAtlas和UIFont中的Material。
2013.11.5 凌晨 增補
新聞熱點
疑難解答