天天看點

NGUI所見即所得之UIAnchor,UIStretch

NGUI的Example/Scenes/Example1介紹的主要就是UIRoot,UIAnchor和UIStretch這三個腳本,當然UIRoot是每個UI都會有的(挂在根節點),之前D.S.Qiu也寫過部落格介紹過UIRoot( 猛點檢視)。一直都對NGUI把Unity的機關和像素的轉換和統一都很有疑惑,因之手遊項目要做UI的自适應,就覺得很有必要把UIAnchor和UIStretch深入的研究下。

        UIRoot在D.S.Qiu的第一篇NGUI的文章中介紹了(猛點檢視),UIRoot其實就做了一件事情:根據Screen.height和UIRoot.activeHeight的比例來調整UIRoot的loaclScal,進而保證UIWidget(UISprite,UILabel)可以按照其本身的大小進行設定,而不用經過複雜的換算過程。

         UIAnchor和UIStretch處理上的細節很相似,都是先計算參照對象(這個參照對象由Insprector的Container指定,如果沒有選擇,就是Camera)的大小Rect,然後根據參數UIAnchor(Side,relativeOffset,pixelOffset),UIStretch(Style,relativeSize,initialSize,borderPadding)進行調整,最後設定對應的屬性,隻不過UIAnchor設定的是transform.position,UIStretch設定的是(width,height)或clipRange等。

UIAnchor

        先看下UIAnchor的Inspector界面,感覺很簡單,不會像UIPanel那麼複雜:

NGUI所見即所得之UIAnchor,UIStretch

        Container:指定Anchor的參照點,如果沒有選擇,則以Camera的pixelRect的區域為參照面

        Side:Anchor的定位方式

        Relative Offset:相對于Camera,或Container的百分比偏移

        Pixel Offset:像素的偏移

C#代碼

NGUI所見即所得之UIAnchor,UIStretch
NGUI所見即所得之UIAnchor,UIStretch
NGUI所見即所得之UIAnchor,UIStretch
  1.        /// <summary> 
  2. /// Relative offset value, if any. For example "0.25" with 'side' set to Left, means 25% from the left side. 
  3. /// </summary> 
  4. public Vector2 relativeOffset = Vector2.zero; 
  5. /// <summary> 
  6. /// Pixel offset value if any. For example "10" in x will move the widget 10 pixels to the right  
  7. /// while "-10" in x is 10 pixels to the left based on the pixel values set in UIRoot. 
  8. /// </summary> 
  9. public Vector2 pixelOffset = Vector2.zero; 
/// <summary>
	/// Relative offset value, if any. For example "0.25" with 'side' set to Left, means 25% from the left side.
	/// </summary>
	public Vector2 relativeOffset = Vector2.zero;
	
	/// <summary>
	/// Pixel offset value if any. For example "10" in x will move the widget 10 pixels to the right 
	/// while "-10" in x is 10 pixels to the left based on the pixel values set in UIRoot.
	/// </summary>
	public Vector2 pixelOffset = Vector2.zero;      

Update函數

       UIAnchor的主要功能實作都在Update函數:

C#代碼

NGUI所見即所得之UIAnchor,UIStretch
NGUI所見即所得之UIAnchor,UIStretch
NGUI所見即所得之UIAnchor,UIStretch
  1. void Update () 
  2.     { 
  3.         if (mAnim != null && mAnim.enabled && mAnim.isPlaying) return; 
  4.         bool useCamera = false; 
  5.         UIWidget wc = (container == null) ? null : container.GetComponent<UIWidget>(); 
  6.         UIPanel pc = (container == null && wc == null) ? null : container.GetComponent<UIPanel>(); 
  7.         if (wc != null) 
  8.         { 
  9.             Bounds b = wc.CalculateBounds(container.transform.parent); 
  10.             mRect.x = b.min.x; 
  11.             mRect.y = b.min.y; 
  12.             mRect.width = b.size.x; 
  13.             mRect.height = b.size.y; 
  14.         } 
  15.         else if (pc != null) 
  16.         { 
  17.             if (pc.clipping == UIDrawCall.Clipping.None) 
  18.             { 
  19.                 // Panel has no clipping -- just use the screen's dimensions 
  20.                 float ratio = (mRoot != null) ? (float)mRoot.activeHeight / Screen.height * 0.5f : 0.5f; 
  21.                 mRect.xMin = -Screen.width * ratio; 
  22.                 mRect.yMin = -Screen.height * ratio; 
  23.                 mRect.xMax = -mRect.xMin; 
  24.                 mRect.yMax = -mRect.yMin; 
  25.             } 
  26.             else 
  27.             { 
  28.                 // Panel has clipping -- use it as the mRect 
  29.                 Vector4 pos = pc.clipRange; 
  30.                 mRect.x = pos.x - (pos.z * 0.5f); 
  31.                 mRect.y = pos.y - (pos.w * 0.5f); 
  32.                 mRect.width = pos.z; 
  33.                 mRect.height = pos.w; 
  34.             } 
  35.         } 
  36.         else if (container != null) 
  37.         { 
  38.             Transform root = container.transform.parent; 
  39.             Bounds b = (root != null) ? NGUIMath.CalculateRelativeWidgetBounds(root, container.transform) : 
  40.                 NGUIMath.CalculateRelativeWidgetBounds(container.transform); 
  41.             mRect.x = b.min.x; 
  42.             mRect.y = b.min.y; 
  43.             mRect.width = b.size.x; 
  44.             mRect.height = b.size.y; 
  45.         } 
  46.         else if (uiCamera != null) 
  47.         { 
  48.             useCamera = true; 
  49.             mRect = uiCamera.pixelRect; 
  50.         } 
  51.         else return; 
  52.         float cx = (mRect.xMin + mRect.xMax) * 0.5f; 
  53.         float cy = (mRect.yMin + mRect.yMax) * 0.5f; 
  54.         Vector3 v = new Vector3(cx, cy, 0f); 
  55.         if (side != Side.Center) 
  56.         { 
  57.             if (side == Side.Right || side == Side.TopRight || side == Side.BottomRight) v.x = mRect.xMax; 
  58.             else if (side == Side.Top || side == Side.Center || side == Side.Bottom) v.x = cx; 
  59.             else v.x = mRect.xMin; 
  60.             if (side == Side.Top || side == Side.TopRight || side == Side.TopLeft) v.y = mRect.yMax; 
  61.             else if (side == Side.Left || side == Side.Center || side == Side.Right) v.y = cy; 
  62.             else v.y = mRect.yMin; 
  63.         } 
  64.         float width = mRect.width; 
  65.         float height = mRect.height; 
  66.         v.x += pixelOffset.x + relativeOffset.x * width; 
  67.         v.y += pixelOffset.y + relativeOffset.y * height; 
  68.         if (useCamera) 
  69.         { 
  70.             if (uiCamera.orthographic) 
  71.             { 
  72.                 v.x = Mathf.Round(v.x); 
  73.                 v.y = Mathf.Round(v.y); 
  74.                 if (halfPixelOffset && mNeedsHalfPixelOffset) 
  75.                 { 
  76.                     v.x -= 0.5f; 
  77.                     v.y += 0.5f; 
  78.                 } 
  79.             } 
  80.             v.z = uiCamera.WorldToScreenPoint(mTrans.position).z; 
  81.             v = uiCamera.ScreenToWorldPoint(v); 
  82.         } 
  83.         else 
  84.         { 
  85.             v.x = Mathf.Round(v.x); 
  86.             v.y = Mathf.Round(v.y); 
  87.             if (pc != null) 
  88.             { 
  89.                 v = pc.cachedTransform.TransformPoint(v); 
  90.             } 
  91.             else if (container != null) 
  92.             { 
  93.                 Transform t = container.transform.parent; 
  94.                 if (t != null) v = t.TransformPoint(v); 
  95.             } 
  96.             v.z = mTrans.position.z; 
  97.         } 
  98.         // Wrapped in an 'if' so the scene doesn't get marked as 'edited' every frame 
  99.         if (mTrans.position != v) mTrans.position = v; 
  100.         if (runOnlyOnce && Application.isPlaying) Destroy(this); 
  101.     } 
void Update ()
	{
		if (mAnim != null && mAnim.enabled && mAnim.isPlaying) return;
		bool useCamera = false;
		UIWidget wc = (container == null) ? null : container.GetComponent<UIWidget>();
		UIPanel pc = (container == null && wc == null) ? null : container.GetComponent<UIPanel>();
		if (wc != null)
		{
			Bounds b = wc.CalculateBounds(container.transform.parent);

			mRect.x = b.min.x;
			mRect.y = b.min.y;

			mRect.width = b.size.x;
			mRect.height = b.size.y;
		}
		else if (pc != null)
		{
			if (pc.clipping == UIDrawCall.Clipping.None)
			{
				// Panel has no clipping -- just use the screen's dimensions
				float ratio = (mRoot != null) ? (float)mRoot.activeHeight / Screen.height * 0.5f : 0.5f;
				mRect.xMin = -Screen.width * ratio;
				mRect.yMin = -Screen.height * ratio;
				mRect.xMax = -mRect.xMin;
				mRect.yMax = -mRect.yMin;
			}
			else
			{
				// Panel has clipping -- use it as the mRect
				Vector4 pos = pc.clipRange;
				mRect.x = pos.x - (pos.z * 0.5f);
				mRect.y = pos.y - (pos.w * 0.5f);
				mRect.width = pos.z;
				mRect.height = pos.w;
			}
		}
		else if (container != null)
		{
			Transform root = container.transform.parent;
			Bounds b = (root != null) ? NGUIMath.CalculateRelativeWidgetBounds(root, container.transform) :
				NGUIMath.CalculateRelativeWidgetBounds(container.transform);

			mRect.x = b.min.x;
			mRect.y = b.min.y;

			mRect.width = b.size.x;
			mRect.height = b.size.y;
		}
		else if (uiCamera != null)
		{
			useCamera = true;
			mRect = uiCamera.pixelRect;
		}
		else return;

		float cx = (mRect.xMin + mRect.xMax) * 0.5f;
		float cy = (mRect.yMin + mRect.yMax) * 0.5f;
		Vector3 v = new Vector3(cx, cy, 0f);

		if (side != Side.Center)
		{
			if (side == Side.Right || side == Side.TopRight || side == Side.BottomRight) v.x = mRect.xMax;
			else if (side == Side.Top || side == Side.Center || side == Side.Bottom) v.x = cx;
			else v.x = mRect.xMin;

			if (side == Side.Top || side == Side.TopRight || side == Side.TopLeft) v.y = mRect.yMax;
			else if (side == Side.Left || side == Side.Center || side == Side.Right) v.y = cy;
			else v.y = mRect.yMin;
		}

		float width = mRect.width;
		float height = mRect.height;

		v.x += pixelOffset.x + relativeOffset.x * width;
		v.y += pixelOffset.y + relativeOffset.y * height;

		if (useCamera)
		{
			if (uiCamera.orthographic)
			{
				v.x = Mathf.Round(v.x);
				v.y = Mathf.Round(v.y);

				if (halfPixelOffset && mNeedsHalfPixelOffset)
				{
					v.x -= 0.5f;
					v.y += 0.5f;
				}
			}

			v.z = uiCamera.WorldToScreenPoint(mTrans.position).z;
			v = uiCamera.ScreenToWorldPoint(v);
		}
		else
		{
			v.x = Mathf.Round(v.x);
			v.y = Mathf.Round(v.y);

			if (pc != null)
			{
				v = pc.cachedTransform.TransformPoint(v);
			}
			else if (container != null)
			{
				Transform t = container.transform.parent;
				if (t != null) v = t.TransformPoint(v);
			}
			v.z = mTrans.position.z;
		}

		// Wrapped in an 'if' so the scene doesn't get marked as 'edited' every frame
		if (mTrans.position != v) mTrans.position = v;
		if (runOnlyOnce && Application.isPlaying) Destroy(this);
	}      

       Update的原理很簡單,梳理歸納為4部分:

                 1)擷取Anchor參照對象的大小Rect以及計算中心點Vector3 v;

                 2)根據Side類型調整v的x,y,z值;

                 3)将v轉換成世界坐标

                 4)将v賦給mTrans.position。

        這裡對1)再多說幾句,主要是涉及參照對象的選取問題,用if - else if來篩選的次序是 UIWidget wc , UIPanel pc , GameObject container, Camera uiCamera,如果前者部位null這取前者的大小後面的就不予考慮。

C#代碼

NGUI所見即所得之UIAnchor,UIStretch
NGUI所見即所得之UIAnchor,UIStretch
NGUI所見即所得之UIAnchor,UIStretch
  1. UIWidget wc = (container == null) ? null : container.GetComponent<UIWidget>(); 
  2. container == null && wc == null) ? null : container.GetComponent<UIPanel>(); 
UIWidget wc = (container == null) ? null : container.GetComponent<UIWidget>();
		UIPanel pc = (container == null && wc == null) ? null : container.GetComponent<UIPanel>();      

像素與Unity機關

        之前項目中使用的NGUI版本還是2.6.3,那個版本還沒有pixelOffset,然後為了實作一個相對便宜就在挂載Anchor的對象下面挂載一個子對象,通過設定子對象的loaclPosition來設定相對偏移:

NGUI所見即所得之UIAnchor,UIStretch
NGUI所見即所得之UIAnchor,UIStretch

      這樣用transform.find去查找某一個子對象的時候就會覺得很蛋疼,是以當看到pixelOffset的就覺得沒有必要用offset這層節點了,這可以說是NGUI埋下隐形的坑(很多沒有“愛參考”不思考的開發者,就喜歡照搬别人的東西),之前的項目就是這樣的,看了一堆Offset,完全沒有必要。然後果斷測試就會有以下不同的情況。

      測試之前首先将上面Bottom/Offset的localPosition置0,并修改稿Bottom的UIAnchor的pixelOffset改為(0,40)

1)當參照對象是Camera時,即Container=null:

NGUI所見即所得之UIAnchor,UIStretch

但當編輯器的分辨率等于某個值時,又回恢複正常情況:

NGUI所見即所得之UIAnchor,UIStretch

2)把Bottom的父對象UIPanel拖給UIAnchor的Container:

NGUI所見即所得之UIAnchor,UIStretch

這種情況是沒有問題的:

NGUI所見即所得之UIAnchor,UIStretch

       回過頭看下Update函數中對pixelOffset的使用:

C#代碼

NGUI所見即所得之UIAnchor,UIStretch
NGUI所見即所得之UIAnchor,UIStretch
NGUI所見即所得之UIAnchor,UIStretch
  1. v.x += pixelOffset.x + relativeOffset.x * width; 
  2. v.y += pixelOffset.y + relativeOffset.y * height; 
v.x += pixelOffset.x + relativeOffset.x * width;
		v.y += pixelOffset.y + relativeOffset.y * height;      

        經過反複的思考,覺得一定是pixelOffset和子對象Offset的localPosition數值的參考系是不一樣的,但最終都是通過mRect來處理的,是以把UIAnchor Rect mRect設定成public,檢視mRect的值,上面三個情況對應mRect值分别如下:

NGUI所見即所得之UIAnchor,UIStretch
NGUI所見即所得之UIAnchor,UIStretch
NGUI所見即所得之UIAnchor,UIStretch

      這說明,當mRect.y大于等于800的時候,使用UIAnchor的pixelOffset和使用子對象Offset的localPosition的表現是一緻的。但為什麼指定Container為UIPanel都不會出現異常情況,隻有Camera會出現。再回到Update看下擷取mRect的方法,指定Container時,mRect實際是UIPanel,或UIWideget的像素大小,其實是UIWidget的(width,height),而沒有指定Container情況下,mRect = camera.pixelRect。

      在UIRoot文中,就說過camera.pixelRect其實就是Screen的(width,height),也就是說,camera.pixelRect是會根據顯示器的分辨率動态改變的,而指定Container時,mRect是不會改變的(在介紹UIRoot文中有介紹)。

       在看下pixelOffset的使用(真的是最後一次了):

C#代碼

NGUI所見即所得之UIAnchor,UIStretch
NGUI所見即所得之UIAnchor,UIStretch
NGUI所見即所得之UIAnchor,UIStretch
  1. v.x += pixelOffset.x + relativeOffset.x * width; 
  2. v.y += pixelOffset.y + relativeOffset.y * height; 
v.x += pixelOffset.x + relativeOffset.x * width;
		v.y += pixelOffset.y + relativeOffset.y * height;      

       雖然pixelOffset的值一直沒有變化,但是當mRect = camera.pixelCamera時,mRect是随着分辨率的變化而變化的,那樣的話pixelOffset占的權重就會改變了,是以才會出現上面的偏移異常。

解決政策

       在UIAnchor中設定一個參數unitOffset來代替子對象Offset的功能:

NGUI所見即所得之UIAnchor,UIStretch

C#代碼

NGUI所見即所得之UIAnchor,UIStretch
NGUI所見即所得之UIAnchor,UIStretch
NGUI所見即所得之UIAnchor,UIStretch
  1. public Vector2 unitOffset = Vector2.zero; 
public Vector2 unitOffset = Vector2.zero;      

       然後把設定的值在Update函數的最後加個 mTrans.localPosition += new Vector3(unitOffset.x,unitOffset.y,0);

C#代碼

NGUI所見即所得之UIAnchor,UIStretch
NGUI所見即所得之UIAnchor,UIStretch
NGUI所見即所得之UIAnchor,UIStretch
  1. void Update() 
  2.        {     
  3.                //省略前面的處理。。。 
  4.                // Wrapped in an 'if' so the scene doesn't get marked as 'edited' every frame 
  5.     if (mTrans.position != v) mTrans.position = v; 
  6.        mTrans.localPosition += new Vector3(unitOffset.x,unitOffset.y,0); 
  7.     if (runOnlyOnce && Application.isPlaying) Destroy(this); 
  8.        } 
void Update()
        {	
                //省略前面的處理。。。
                // Wrapped in an 'if' so the scene doesn't get marked as 'edited' every frame
		if (mTrans.position != v) mTrans.position = v;
        mTrans.localPosition += new Vector3(unitOffset.x,unitOffset.y,0);
		if (runOnlyOnce && Application.isPlaying) Destroy(this);
        }      

        這樣就可以輕松取得GameObject樹中Offset一層,之前項目中就有這個一層,我看着就來火,終于幹掉了哈……

        還有一個問題:為什麼當camera.pixelRect.y等于800時,就會恢複正常,這個先看下UIRoot的設定(對UIRoot原理不了解請猛擊):

NGUI所見即所得之UIAnchor,UIStretch

        Manual Height設定為800,Scaling Style設定為FixedSize,可以知道UI的放縮參考高度就是 800,即實際UI布局高度就是800,這裡有點難了解,總之就是當螢幕分辨率高度等于800時,pixelOffset和子對象Offset的localPostion參考點就一緻了,實際效果就一樣了。也可以這麼解釋:當mRect = camera.pixelRect 時,pixeloffset的值是相對于camera.pixelRect而言的,在螢幕的呈現是會對着屏蔽的分辨率不同而改變的;而使用子對象Offset的localPosition的參照和UI元件是一緻的,是以相對于Contaner的位置是不會改變的。

        回到文中開頭抛出的一個問題——Unity中transfrom的機關和像素的關系,上張圖檔,可以知道UI的高度實際高度是800,然後看下Anchor - Top 和Anchor - Bottom的transform的localPostion.y分别是400.5006和-399.4994(圖檔如下),發現兩者區間剛好是800,這就說明NGUI的架構中像素坐标和Unity的機關是一緻的,對等的,即一個Unity機關等于一個像素。

NGUI所見即所得之UIAnchor,UIStretch
NGUI所見即所得之UIAnchor,UIStretch

UIStretch

       看下NGUI Example1 的Anchor - Stretch的Inspector面闆,發現UIStretch隻比UIAnchor多了一個參數,不過從這張圖UISprite的Dimension(紅框标出)的長度始終都是800,不管螢幕如何改變大小,都是800個像素,填充的Tiled圖檔個數也是一樣的,這也更加說明了上面提到的一個結論:NGUI中Unity的機關和像素是同一的。

NGUI所見即所得之UIAnchor,UIStretch

Update

       UIStretch的Update函數的前面部分跟UIAnchor的Update的前面部分原理是一樣的都是擷取mRect,是以隻看後一部分的實作(了解 relativeSize 和 initialSize 的含義和作用):

C#代碼

NGUI所見即所得之UIAnchor,UIStretch
NGUI所見即所得之UIAnchor,UIStretch
NGUI所見即所得之UIAnchor,UIStretch
  1. void Update() 
  2.                         //.......省略上面的處理 
  3.             float rectWidth = mRect.width; 
  4.             float rectHeight = mRect.height; 
  5.             if (adjustment != 1f && rectHeight > 1f) 
  6.             { 
  7.                 float scale = mRoot.activeHeight / rectHeight; 
  8.                 rectWidth *= scale; 
  9.                 rectHeight *= scale; 
  10.             } 
  11.             Vector3 size = (mWidget != null) ? new Vector3(mWidget.width, mWidget.height) : mTrans.localScale; 
  12.             if (style == Style.BasedOnHeight) 
  13.             { 
  14.                 size.x = relativeSize.x * rectHeight; 
  15.                 size.y = relativeSize.y * rectHeight; 
  16.             } 
  17.             else if (style == Style.FillKeepingRatio) 
  18.             { 
  19.                 // Contributed by Dylan Ryan 
  20.                 float screenRatio = rectWidth / rectHeight; 
  21.                 float imageRatio = initialSize.x / initialSize.y; 
  22.                 if (imageRatio < screenRatio) 
  23.                 { 
  24.                     // Fit horizontally 
  25.                     float scale = rectWidth / initialSize.x; 
  26.                     size.x = rectWidth; 
  27.                     size.y = initialSize.y * scale; 
  28.                 } 
  29.                 else 
  30.                 { 
  31.                     // Fit vertically 
  32.                     float scale = rectHeight / initialSize.y; 
  33.                     size.x = initialSize.x * scale; 
  34.                     size.y = rectHeight; 
  35.                 } 
  36.             } 
  37.             else if (style == Style.FitInternalKeepingRatio) 
  38.             { 
  39.                 // Contributed by Dylan Ryan 
  40.                 float screenRatio = rectWidth / rectHeight; 
  41.                 float imageRatio = initialSize.x / initialSize.y; 
  42.                 if (imageRatio > screenRatio) 
  43.                 { 
  44.                     // Fit horizontally 
  45.                     float scale = rectWidth / initialSize.x; 
  46.                     size.x = rectWidth; 
  47.                     size.y = initialSize.y * scale; 
  48.                 } 
  49.                 else 
  50.                 { 
  51.                     // Fit vertically 
  52.                     float scale = rectHeight / initialSize.y; 
  53.                     size.x = initialSize.x * scale; 
  54.                     size.y = rectHeight; 
  55.                 } 
  56.             } 
  57.             else 
  58.             { 
  59.                 if (style != Style.Vertical) 
  60.                     size.x = relativeSize.x * rectWidth; 
  61.                 if (style != Style.Horizontal) 
  62.                     size.y = relativeSize.y * rectHeight; 
  63.             } 
  64.             if (mSprite != null) 
  65.             { 
  66.                 float multiplier = (mSprite.atlas != null) ? mSprite.atlas.pixelSize : 1f; 
  67.                 size.x -= borderPadding.x * multiplier; 
  68.                 size.y -= borderPadding.y * multiplier; 
  69.                 if (style != Style.Vertical) 
  70.                     mSprite.width = Mathf.RoundToInt(size.x); 
  71.                 if (style != Style.Horizontal) 
  72.                     mSprite.height = Mathf.RoundToInt(size.y); 
  73.                 size = Vector3.one; 
  74.             } 
  75.             else if (mWidget != null) 
  76.             { 
  77.                 if (style != Style.Vertical) 
  78.                     mWidget.width = Mathf.RoundToInt(size.x - borderPadding.x); 
  79.                 if (style != Style.Horizontal) 
  80.                     mWidget.height = Mathf.RoundToInt(size.y - borderPadding.y); 
  81.                 size = Vector3.one; 
  82.             } 
  83.             else if (mPanel != null) 
  84.             { 
  85.                 Vector4 cr = mPanel.clipRange; 
  86.                 if (style != Style.Vertical) 
  87.                     cr.z = size.x - borderPadding.x; 
  88.                 if (style != Style.Horizontal) 
  89.                     cr.w = size.y - borderPadding.y; 
  90.                 mPanel.clipRange = cr; 
  91.                 size = Vector3.one; 
  92.             } 
  93.             else 
  94.             { 
  95.                 if (style != Style.Vertical) 
  96.                     size.x -= borderPadding.x; 
  97.                 if (style != Style.Horizontal) 
  98.                     size.y -= borderPadding.y; 
  99.             } 
  100.             if (mTrans.localScale != size) 
  101.                 mTrans.localScale = size; 
  102.             if (runOnlyOnce && Application.isPlaying) Destroy(this); 
void Update()
{
                        //.......省略上面的處理
			float rectWidth = mRect.width;
			float rectHeight = mRect.height;

			if (adjustment != 1f && rectHeight > 1f)
			{
				float scale = mRoot.activeHeight / rectHeight;
				rectWidth *= scale;
				rectHeight *= scale;
			}

			Vector3 size = (mWidget != null) ? new Vector3(mWidget.width, mWidget.height) : mTrans.localScale;

			if (style == Style.BasedOnHeight)
			{
				size.x = relativeSize.x * rectHeight;
				size.y = relativeSize.y * rectHeight;
			}
			else if (style == Style.FillKeepingRatio)
			{
				// Contributed by Dylan Ryan
				float screenRatio = rectWidth / rectHeight;
				float imageRatio = initialSize.x / initialSize.y;

				if (imageRatio < screenRatio)
				{
					// Fit horizontally
					float scale = rectWidth / initialSize.x;
					size.x = rectWidth;
					size.y = initialSize.y * scale;
				}
				else
				{
					// Fit vertically
					float scale = rectHeight / initialSize.y;
					size.x = initialSize.x * scale;
					size.y = rectHeight;
				}
			}
			else if (style == Style.FitInternalKeepingRatio)
			{
				// Contributed by Dylan Ryan
				float screenRatio = rectWidth / rectHeight;
				float imageRatio = initialSize.x / initialSize.y;

				if (imageRatio > screenRatio)
				{
					// Fit horizontally
					float scale = rectWidth / initialSize.x;
					size.x = rectWidth;
					size.y = initialSize.y * scale;
				}
				else
				{
					// Fit vertically
					float scale = rectHeight / initialSize.y;
					size.x = initialSize.x * scale;
					size.y = rectHeight;
				}
			}
			else
			{
				if (style != Style.Vertical)
					size.x = relativeSize.x * rectWidth;

				if (style != Style.Horizontal)
					size.y = relativeSize.y * rectHeight;
			}

			if (mSprite != null)
			{
				float multiplier = (mSprite.atlas != null) ? mSprite.atlas.pixelSize : 1f;
				size.x -= borderPadding.x * multiplier;
				size.y -= borderPadding.y * multiplier;

				if (style != Style.Vertical)
					mSprite.width = Mathf.RoundToInt(size.x);

				if (style != Style.Horizontal)
					mSprite.height = Mathf.RoundToInt(size.y);

				size = Vector3.one;
			}
			else if (mWidget != null)
			{
				if (style != Style.Vertical)
					mWidget.width = Mathf.RoundToInt(size.x - borderPadding.x);

				if (style != Style.Horizontal)
					mWidget.height = Mathf.RoundToInt(size.y - borderPadding.y);

				size = Vector3.one;
			}
			else if (mPanel != null)
			{
				Vector4 cr = mPanel.clipRange;

				if (style != Style.Vertical)
					cr.z = size.x - borderPadding.x;
				
				if (style != Style.Horizontal)
					cr.w = size.y - borderPadding.y;
				
				mPanel.clipRange = cr;
				size = Vector3.one;
			}
			else
			{
				if (style != Style.Vertical)
					size.x -= borderPadding.x;
				
				if (style != Style.Horizontal)
					size.y -= borderPadding.y;
			}
			
			if (mTrans.localScale != size)
				mTrans.localScale = size;

			if (runOnlyOnce && Application.isPlaying) Destroy(this);
}      

整體放縮

     分析了 UIStretch 的 Update ,可以知道當 UIStretch 挂載的GameObject,有UIWidget,UISprite,UIPanel 這個幾個腳本是,是不會放縮這個GameObject的子GameObject的,如果要整體放縮的話,就得通過倒數第二行:

C#代碼

NGUI所見即所得之UIAnchor,UIStretch
NGUI所見即所得之UIAnchor,UIStretch
NGUI所見即所得之UIAnchor,UIStretch
  1. mTrans.localScale = size;   
mTrans.localScale = size;        

       如果 Style 選擇為 Both ,并且沒有指定Container 且GameObject 上沒有挂載UIWidget,UISprite,UIPanel ,抽出主要的執行代碼:

C#代碼

NGUI所見即所得之UIAnchor,UIStretch
NGUI所見即所得之UIAnchor,UIStretch
NGUI所見即所得之UIAnchor,UIStretch
  1. void Update() 
  2.                         //省略前面的代碼 
  3.                        else if (uiCamera != null) 
  4.             { 
  5.                 mRect = uiCamera.pixelRect; 
  6.                 if (mRoot != null) adjustment = mRoot.pixelSizeAdjustment; 
  7.             } 
  8.             else return; 
  9.             float rectWidth = mRect.width; 
  10.             float rectHeight = mRect.height; 
  11.             if (adjustment != 1f && rectHeight > 1f) 
  12.             { 
  13.                 float scale = mRoot.activeHeight / rectHeight; 
  14.                 rectWidth *= scale; 
  15.                 rectHeight *= scale; 
  16.             } 
  17.                         //省略 代碼 
  18.                         else 
  19.             { 
  20.                 if (style != Style.Vertical) 
  21.                     size.x = relativeSize.x * rectWidth; 
  22.                 if (style != Style.Horizontal) 
  23.                     size.y = relativeSize.y * rectHeight; 
  24.             } 
  25.                         //省略 代碼 
  26.                         if (mTrans.localScale != size) 
  27.                 mTrans.localScale = size; 
void Update()
{
                        //省略前面的代碼
                       else if (uiCamera != null)
			{
				mRect = uiCamera.pixelRect;
				if (mRoot != null) adjustment = mRoot.pixelSizeAdjustment;
			}
			else return;

			float rectWidth = mRect.width;
			float rectHeight = mRect.height;

			if (adjustment != 1f && rectHeight > 1f)
			{
				float scale = mRoot.activeHeight / rectHeight;
				rectWidth *= scale;
				rectHeight *= scale;
			}

                        //省略 代碼
                        else
			{
				if (style != Style.Vertical)
					size.x = relativeSize.x * rectWidth;

				if (style != Style.Horizontal)
					size.y = relativeSize.y * rectHeight;
			}
                        //省略 代碼
                        if (mTrans.localScale != size)
				mTrans.localScale = size;
}      

       整理下: mTrans.localScale.x = relativeSize.x * rectWidth * mRoot.activeHeight / rectHeight

mTrans.localScale.y = relativeSize.y * rectHeight * mRoot.activeHeight / rectHeight = relativeSize.y * mRoot .activeHeight

       因為 UIRoot 是基于高度調整 localScale 的 來做放縮的,是以寬度的放縮UIRoot 已經做了,是以隻需要将 relativeSize.y 設定為 1 / mRoot.activeHeight 使 mTrans.localScale.y = 1恒成立。UIRoot 會是高度始終滿屏(UIRoot Style 為 FixedSize ),但寬度的放縮總是按照高度的放縮比例在放的,是以會出現寬度沒有全部顯示出來,或者左右兩邊有黑邊。

      其實要想做到滿屏(高度和寬度)縮放的效果,其實可以在UIRoot中增加一個 manualWidth 來調整 UIRoot 的localSize.x 的值。

      另外一種做法就是使用UIStretch ,我們隻需要通過設定 relativeSize.x = 1 / manualWidth ;relativeSize.y = 1 / mRoot.activeHeight 就能滿屏縮放了,哈哈,搞定了。等等,這樣是滿屏了,但是其他圖檔或文字會被拉伸變形,也就是說 UIStretch 隻能做到某個單一的元件按比例縮放。

      總之,實際螢幕顯示的寬度 = (Screen.Height / mRoot.acriveHeight * manualHeight > Screen.Width ) ?  Screen.Width :Screen.Height / mRoot.acriveHeight * manualHeight ,就是去兩者中的更小的。是以要做寬度放縮,隻要針對實際顯示寬度 和 螢幕寬度(Screen.Width) 來調整 localScale.x 值就行了。

                                                                                                                                                                                         增補于 2013/11/26 19:45

小結:

       本來昨天晚上就想寫的,但是由于心情不好,也還要一些要點沒有相同,是以才拖到現在完成,今天上了半天班(雖然一直都是在NBA的,今天的詹姆斯太牛逼了,看到我心情都好了,18投14中,罰球11中10,39分),有點小不敬業,吃完中飯之後,就開始組織些了,一直寫到現在(花了5個小時),截了好些圖,這篇應該是D.S.Qiu這麼多篇中寫得最暢快最爽的一次,解決之前的疑問。

       和UIRoot一樣,UIAnchor和UIStretch很簡單,但是卻很重要,雖然就幾個參數,但是要完全明白和了解還是需要花點時間的,是以我才寫出來跟大家分享的,NGUI的文章頁寫了幾篇了(點選檢視),轉載注明出處,尊重原創。

        如果您對D.S.Qiu有任何建議或意見可以在文章後面評論,或者發郵件([email protected])交流,您的鼓勵和支援是我前進的動力,希望能有更多更好的分享。

        轉載請在文首注明出處:http://dsqiu.iteye.com/blog/1975972

更多精彩請關注D.S.Qiu的部落格和微網誌(ID:靜水逐風)