NGUI所见即所得之UIRoot

来源:互联网 发布:rimworld mac mod 编辑:程序博客网 时间:2024/05/01 21:43

 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有任何建议或意见可以在文章后面评论,或者发邮件(gd.s.qiu@gmail.com)交流,您的鼓励和支持是我前进的动力,希望能有更多更好的分享。

 

        转载请在文首注明出处: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

③midashao:http://blog.csdn.net/midashao/article/details/8232341

0 0
原创粉丝点击