NGUI所見即所得之UIRoot

 NGUI所見即所得之UIRoot

         

       UIRoot是NGUI最根本和最重要的腳本,在實際UI開發過程中都是以UIRoot爲根的GameObject樹,那他的作用到底是什麼,先看下UIRoot的Inspection選項:

看到這個,大致可以猜到是跟UI界面縮放有關的,而且是基於高度放縮的。

 

Scaling Style參數

 

       到底每個參數(Scaling Style, Manual Height ,Minimum Height 和MaximumHight)的作用和區別是什麼,在沒有其他先驗知識的情況下,只有去看代碼了。

       

C#代碼  收藏代碼
  1. public enum Scaling  
  2.     {  
  3.         PixelPerfect,  
  4.         FixedSize,  
  5.         FixedSizeOnMobiles,  
  6.     }  

       Scaling Style參數的作用是制定UIRoot的縮放類型,如果是PixelPerfect,Minimum Height和Maximum Height才起作用,換而言之,如果Scaling Style選擇的是PixelPerfect就要對Minimum Height和Maximum Height進行設置。FixedSize和FixedSizeOnMobiles只跟Manual Height有關,區別在於後者(FixedSizeOnMobiles)只是針對IOS和Android上的判斷,也就是說只有IOS和Android平臺下FixedSizeOnMobiles才起作用。

       那他們是怎麼縮放的呢?如果是PixelPerfect縮放類型,當屏幕的分辨率大於Maximum Height,則以Maximum Height 爲基礎縮放,反之,如果屏幕分辨率小於Minimum Height 則以Minimum Height爲基礎進行縮放。例如,如果屏幕高度爲1000,而設置的Maximum Height值爲800,則UI界面整體放大爲原來的1000/800=1.25倍。

       如果Scaling Style指定爲FixedSize或FixedSizeOnMobiles,則縮放只以Manual Height爲參考,屏幕分辨率的高度值不同於此設置值時,則根據其比例(即Screen Height / Manual Height)對整棵UI樹的進行“等比”縮放(寬度的縮放比也是此比例值)。

 

      如果Scaling Style指定爲FixedSize,UIWidget.height(以UIRoot默認進行高度縮放)是不會改變的(有關UIWidget的內容可以查看D.S.Qiu的另外一篇文章),不管實際屏幕分辨率的像素是多少,這看下Example 1 的 Anchor Stretch的背景圖片,高度始終都是800,即設置的的manualHeight:

        也就是相當於UIRoot下的UIWidget的height參數一直都是實際的值,雖然在實際在顯示器顯示的高度不是UIWidget.height這個值,所以纔有了放縮的感覺。實際的放縮是根據Camera.pixelHeight(這個值和Screen.height的大小是一樣的)來的,也就是放縮比 = Camera.pixelHeight/UIRoot.manualHeight,或者是Screen.height/UIRoot.manualHeight。

         也就是說,當Scaling Style 指定爲FixedSize,UIRoot的子對象高度的參數保持不變,至於顯示的縮放是根據Camera自動進行的,程序無需額外控制。更多詳細的分享可以參考另外一篇有關UIAnchor 和UIStretch的文章(猛點查看)。

                                                                                                                                           增補於 2013/11/16 14:20

 

Scaling Style策略       

 

(1)PixelPerfect和Minimum Height, Maximum Height

使用PixelPefect,只要是想達到UI圖片儘可能不縮放,保持原尺寸大小,這種在PC端這種可以調節界面大小的會使用比較多。

(2)FixedSize和Manul Height

FixedSize只要是希望UI界面儘可能和屏幕分辨率適配,如移動設備上,特別是手機上,屏幕就那麼小,UI界面一定要求全屏顯示,就要進行縮放。

 

       對於Unity實際開發中屏幕自適應問題,oneRain在①中有更詳細的描述,這裏只介紹下,D.S.Qiu想到的一種自適應策略——“花草”填充法。填充法(這個是D.S.Qiu命名的,哈哈,自戀下)指的是用其他圖片區填充因爲固定比例放縮而出現鏤空黑邊的區域,當然也可能已經有遊戲是這麼做的,當然oneRain說的增加一個寬度縮放比例,長寬分別以儘可能接近屏幕長寬比的比例去縮放。

 

Scale的實現

       雖然我們已經瞭解了Scale的作用區別以及策略,那到底是怎麼實現的呢?還是上代碼:

C#代碼  收藏代碼
  1. void Update ()  
  2.     {  
  3.         if (mTrans != null)  
  4.         {  
  5.             float calcActiveHeight = activeHeight;  
  6.             if (calcActiveHeight > 0f )  
  7.             {  
  8.                 float size = 2f / calcActiveHeight;  
  9.                 Vector3 ls = mTrans.localScale;  
  10.       
  11.                 if (!(Mathf.Abs(ls.x - size) <= float.Epsilon) ||  
  12.                     !(Mathf.Abs(ls.y - size) <= float.Epsilon) ||  
  13.                     !(Mathf.Abs(ls.z - size) <= float.Epsilon))  
  14.                 {  
  15.                     mTrans.localScale = new Vector3(size, size, size);  
  16.                 }  
  17.             }  
  18.         }  
  19.     }  

       可以看出Update函數中是根據activeHeight來調整UIRoot的transform的localScale來實現的,哈,竟可以這麼簡單。那麼,我們只要弄清楚activeHeight就可以了:

C#代碼  收藏代碼
  1. public int activeHeight  
  2.     {  
  3.         get  
  4.         {  
  5.             int height = Mathf.Max(2, Screen.height);  
  6.             if (scalingStyle == Scaling.FixedSize) return manualHeight;  
  7.  
  8. #if UNITY_IPHONE || UNITY_ANDROID  
  9.             if (scalingStyle == Scaling.FixedSizeOnMobiles)  
  10.                 return manualHeight;  
  11. #endif  
  12.             if (height < minimumHeight) return minimumHeight;  
  13.             if (height > maximumHeight) return maximumHeight;  
  14.             return height;  
  15.         }  
  16.     }  

       可以看出activeHeight就是前面Scale Style的不同參數的具體實現,即得到縮放參考高度。

 

Orthographic Size和分辨率

       

       在上面Update和activeHeight的函數中都出現了  “2” 這個常數,這個常數到底是怎麼來的。要想知道這個,就要明白Camera 設定爲Orthographic類型中的Size(即Orthographic Size)的含義,查看Unity文檔,就可以知道這個Size是Camera看到區域的一半,如果Size設置爲1,則Camera可以看到高度爲爲2的區域,然後我們知道照相機看到的區域是全畫在整個屏幕的,也就是說Size的值對應爲屏幕分辨率的一半。

        如果屏幕寬度爲1000個像素,Size設置的值表示1000/2=500個像素。所以,我們通過整個關係計算UIRoot下的GameObject的實際對應屏幕的高度:從GameObject向上一直到UIRoot,將它們的loaclScal相乘得到的乘積除以Size乘以屏幕高度的一半,即(localScale*....localScale)/Size*Screen.height/2。

        這可以解釋UIRoot的localStyle爲啥都是很小的小數,因爲這樣可以保證UIRoot的子節點都可以以原來的大小作爲localScale,比如一張圖片是20*20的,我們可以直接設置localScale爲(20,20,1)不用進行換算,直觀方便。(NGUI3.0(or 2.7)以後的版本已經不再使用localScale來表示UISprite ,UILable(UIWidget的子類)的大小了,而是在UIWidget的width和height來設置,這樣做的好處就是一個gameObject節點可以掛多個UISprite或UILabel了,而不會受localScale的衝突影響    2013/11/16增補)。

 

 

UIRoot細節

      前面說道Update函數中有一個常數 2 ,表示Size是設置爲1的,這個可以從Start函數就可以知道:

C#代碼  收藏代碼
  1. protected virtual void Start ()  
  2.     {  
  3.         UIOrthoCamera oc = GetComponentInChildren<UIOrthoCamera>();  
  4.         if (oc != null)  
  5.         {  
  6.             Debug.LogWarning("UIRoot should not be active at the same time as UIOrthoCamera. Disabling UIOrthoCamera.", oc);  
  7.             Camera cam = oc.gameObject.GetComponent<Camera>();  
  8.             oc.enabled = false;  
  9.             if (cam != null) cam.orthographicSize = 1f;  
  10.         }  
  11.         else Update();  
  12.     }  

      但是這裏似乎有點疏忽,這裏只移除UIOrthoCamera這個腳本(UIRoot腳本開始就言明這兩個腳本不能同時使用,所以要移除),並將cam的orthographicSize設置爲1f,但是我想如果沒有UIorthoCamera這個腳本的話,就不會重新設置Camera的orthographicSize值,這樣如果orthographicSize不是1的話,效果就不一樣了。剛開始我會以爲這是NGUI developer的一個Bug,但是如果都在Start設置orthographicSize爲1f,那這個參數就沒有意義了,讓使用者自己設置可以有更多效果,如“屏中屏”——將滿屏的UI縮放爲另外一個UI界面的一半大小,所以纔會說“似乎有點疏忽”。

      下面看下效果圖:


  orthographicSize=1
  orthographicSize=2

        很明顯可以orthographicSize=2時,圖片進行了縮小。當orthographicSize=1時背景圖片用了UIStretch腳本來實現滿屏效果,當orthographicSize爲2時卻沒有滿屏,這就說明代碼中UIRoot是以2爲屏幕寬度的,現在Camera的視野大小爲4,那映射到屏幕當然不會有“滿屏”的效果了(只會是屏幕寬度的一半),背景圖片在左上角是因爲使用UIAnchor腳本。

         ②和③中分別都介紹瞭如何設置Orthographic Size來達到像素和Unity中單位對應起來,都寫的很不錯,這也是我寫這篇博客的一個觸動。

     

         UIRoot中還有兩類函數:GetPixelSizeAdjustment 和 Broadcast,前者是獲取當前分辨率的單個像素的大小,而後者其實就一個UIRoot的消息廣播函數,還有一個當前激活狀態下的UIRoot的隊列。至此D.S.Qiu已經將UIRoot腳本解析的淋漓盡致了,自然就剩下小結了。

 

基於寬度放縮

       

        UIRoot是基於高度放縮的,即放縮的比例是以高度爲參考的,所以UIRoot有一個manualHeight的參數。那麼對於橫版遊戲顯然不行,要是能實現基於寬度放縮。所以可以通過設置一個“manualWidth”的參數來做。比如我們項目中使用的是 1024 作爲UI的寬屏尺寸,通過換算設置manualHeight的值:

 

C#代碼  收藏代碼
  1. int height = Mathf.Max(2, Screen.height);  
  2. manualHeight = Screen.height * 1024 / Screen.width; //基於寬度的屏幕分辨率自適應  

        之前的同事用了很多方法都沒有搞定,其實根本就沒有那麼複雜,所以一旦知道原理之後,很多事情就變得簡單多了。

                                                                                                                     增補於  2013,12,23             22:14

小結:

        一直都想把NGUI的內部機制徹底的弄明白,一直都沒付諸實踐,知道看②和③中的文章,D.S.Qiu發現還是有必要儘快整理下,切好最近項目沒什麼事情,也是週末。UIRoot這個腳本雖然很簡單,但確實NGUI整個體系的基石。更多NGUI文章點擊查看。

 

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

 

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

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

        

        

 

 

       

        

 

 

參考:

oneRain: http://blog.csdn.net/onerain88/article/details/11713299

風宇衝: http://blog.sina.com.cn/s/blog_471132920101fua3.html

midashaohttp://blog.csdn.net/midashao/article/details/8232341

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章