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來區分:
- public enum Type
- {
- Simple,
- Sliced,
- Tiled,
- Filled,
- }
UISprite主要是重寫(override)OnFill函數——把Vertices,UVs和Colors添加進UIGeometry中。在分析OnFill之前,先看下drawingDimensions的實現:
- /// <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函數,在代碼中給出註釋:
- 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的情況來繪製的。
- 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():
- /// <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;
- }
- }
這裏涉及的變量比較多,做下簡單的列舉:
- 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進行填充,就不貼代碼了,有需求可以仔細研究下……
『文本縮放樣式:
- 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的代碼:
- v0.x = x + mTempChar.vert.xMin;
- v0.y = y + mTempChar.vert.yMax - baseline;
只要細緻的,都能看出有問題,x和y都是絕對值(正),所以如果x方向是 + ,那麼 y方向就應該是 -。
然後又去看了之前的版本發現:
- 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有任何建議或意見可以在文章後面評論,或者發郵件([email protected])交流,您的鼓勵和支持是我前進的動力,希望能有更多更好的分享。
轉載請在文首註明出處: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 凌晨 增補