NGUI所见即所得之UISprite & UILabel

阅读更多

       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来区分:

public enum Type
	{
		Simple,
		Sliced,
		Tiled,
		Filled,
	}

        UISprite主要是重写(override)OnFill函数——把Vertices,UVs和Colors添加进UIGeometry中。在分析OnFill之前,先看下drawingDimensions的实现:

        /// 
	/// 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.
	/// 

	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 verts, BetterList uvs, BetterList 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 verts, BetterList uvs, BetterList 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():

         /// 
	/// Process the raw text, called when something changes.
	/// 

	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);
			v0.y = -(y - mTempChar.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 凌晨   增补

 

 

 

 

 

你可能感兴趣的:(NGUI所见即所得之UISprite & UILabel)