国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 學院 > 開發設計 > 正文

NGUI所見即所得之UIAnchor & UIStretch

2019-11-09 17:31:27
字體:
來源:轉載
供稿:網友

   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那么復雜:        Container:指定Anchor的參照點,如果沒有選擇,則以Camera的pixelRect的區域為參照面

        Side:Anchor的定位方式

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

        Pixel Offset:像素的偏移

C#代碼  收藏代碼       /// <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#代碼  收藏代碼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#代碼  收藏代碼UIWidget wc = (container == null) ? null : container.GetComponent<UIWidget>();  container == null && wc == null) ? null : container.GetComponent<UIPanel>();  

 

像素與Unity單位

        之前項目中使用的NGUI版本還是2.6.3,那個版本還沒有pixelOffset,然后為了實現一個相對便宜就在掛載Anchor的對象下面掛載一個子對象,通過設置子對象的loaclPosition來設置相對偏移:      這樣用transform.find去查找某一個子對象的時候就會覺得很蛋疼,所以當看到pixelOffset的就覺得沒有必要用offset這層節點了,這可以說是NGUI埋下隱形的坑(很多沒有“愛參考”不思考的開發者,就喜歡照搬別人的東西),之前的項目就是這樣的,看了一堆Offset,完全沒有必要。然后果斷測試就會有以下不同的情況。

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

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

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

 

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

這種情況是沒有問題的:

 

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

C#代碼  收藏代碼v.x += pixelOffset.x + relativeOffset.x * width;  v.y += pixelOffset.y + relativeOffset.y * height;  

        經過反復的思考,覺得一定是pixelOffset和子對象Offset的localPosition數值的參考系是不一樣的,但最終都是通過mRect來處理的,所以把UIAnchor Rect mRect設置成public,查看mRect的值,上面三個情況對應mRect值分別如下:      這說明,當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#代碼  收藏代碼v.x += pixelOffset.x + relativeOffset.x * width;  v.y += pixelOffset.y + relativeOffset.y * height;  

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

       

解決策略

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

C#代碼  收藏代碼public Vector2 unitOffset = Vector2.zero;  

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

C#代碼  收藏代碼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原理不了解請猛擊):

        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單位等于一個像素。UIStretch

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

 Update

       UIStretch的Update函數的前面部分跟UIAnchor的Update的前面部分原理是一樣的都是獲取mRect,所以只看后一部分的實現(理解 relativeSize 和 initialSize 的含義和作用):

C#代碼  收藏代碼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#代碼  收藏代碼mTrans.localScale = size;    

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

C#代碼  收藏代碼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有任何建議或意見可以在文章后面評論,或者發郵件(gd.s.qiu@Gmail.com)交流,您的鼓勵和支持是我前進的動力,希望能有更多更好的分享。

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

更多精彩請關注D.S.Qiu的博客和微博(ID:靜水逐風) 

       


上一篇:反射

下一篇:NDK的環境搭建 - HelloWordNDK

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 福鼎市| 上林县| 牙克石市| 平果县| 肇源县| 淮北市| 嵊泗县| 独山县| 无棣县| 万荣县| 新化县| 乌兰浩特市| 安陆市| 来宾市| 枞阳县| 南皮县| 馆陶县| 个旧市| 漳浦县| 阳泉市| 宣汉县| 广河县| 盐山县| 米脂县| 敦化市| 兴化市| 商南县| 平度市| 酒泉市| 南漳县| 永丰县| 子洲县| 黔南| 张家口市| 星子县| 海宁市| 文登市| 鸡西市| 商都县| 聂拉木县| 厦门市|