UIRoot是NGUI最根本和最重要的脚本,在实际UI开发过程中都是以UIRoot为根的GameObject树,那他的作用到底是什么,先看下UIRoot的Inspection选项:
看到这个,大致可以猜到是跟UI界面缩放有关的,而且是基于高度放缩的。
Scaling Style参数
到底每个参数(Scaling Style, Manual Height ,Minimum Height 和MaximumHight)的作用和区别是什么,在没有其他先验知识的情况下,只有去看代码了。
public enum Scaling { PixelPerfect, FixedSize, FixedSizeOnMobiles, }
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的作用区别以及策略,那到底是怎么实现的呢?还是上代码:
void Update () { if (mTrans != null) { float calcActiveHeight = activeHeight; if (calcActiveHeight > 0f ) { float size = 2f / calcActiveHeight; Vector3 ls = mTrans.localScale; if (!(Mathf.Abs(ls.x - size) <= float.Epsilon) || !(Mathf.Abs(ls.y - size) <= float.Epsilon) || !(Mathf.Abs(ls.z - size) <= float.Epsilon)) { mTrans.localScale = new Vector3(size, size, size); } } } }
可以看出Update函数中是根据activeHeight来调整UIRoot的transform的localScale来实现的,哈,竟可以这么简单。那么,我们只要弄清楚activeHeight就可以了:
public int activeHeight { get { int height = Mathf.Max(2, Screen.height); if (scalingStyle == Scaling.FixedSize) return manualHeight; #if UNITY_IPHONE || UNITY_ANDROID if (scalingStyle == Scaling.FixedSizeOnMobiles) return manualHeight; #endif if (height < minimumHeight) return minimumHeight; if (height > maximumHeight) return maximumHeight; return height; } }
可以看出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函数就可以知道:
protected virtual void Start () { UIOrthoCamera oc = GetComponentInChildren<UIOrthoCamera>(); if (oc != null) { Debug.LogWarning("UIRoot should not be active at the same time as UIOrthoCamera. Disabling UIOrthoCamera.", oc); Camera cam = oc.gameObject.GetComponent<Camera>(); oc.enabled = false; if (cam != null) cam.orthographicSize = 1f; } else Update(); }
但是这里似乎有点疏忽,这里只移除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的值:
int height = Mathf.Max(2, Screen.height); 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
相关推荐
NGUI的主要组件包括UIRoot、UICamera、Widget和UIWidget。UIRoot是UI系统的根节点,负责管理所有的UI元素;UICamera负责渲染UI元素;Widget是UI的基本单元,可以是按钮、文本、图片等;UIWidget则是所有2D元素的基类...
在场景视图中看到的就是在游戏视图中得到的(所见即所得)。 基于组件的、模块化的特性:要让你的界面控件做什么,只需为其附加相应的行为,而不需要编码。 全面支持iOS/Android和Flash。 灵活的事件系统。 可以让...
在场景视图中看到的就是在游戏视图中得到的(所见即所得)。 基于组件的、模块化的特性:要让你的界面控件做什么,只需为其附加相应的行为,而不需要编码。 全面支持iOS/Android和Flash。 灵活的事件系统。 可以让...
在场景视图中看到的就是在游戏视图中得到的(所见即所得)。 基于组件的、模块化的特性:要让你的界面控件做什么,只需为其附加相应的行为,而不需要编码。 全面支持iOS/Android和Flash。 灵活的事件系统。 可以让...
开发者可以在Unity编辑器中直接设计和调整UI元素,实现所见即所得的效果。这种设计方式极大地提高了开发效率,使得UI设计和游戏逻辑的开发可以同步进行,减少了开发者在不同工具间切换的时间成本。 二、本地化与...
使用unity原生GUI封装,来达到UGUI,NGUI所见即所得的效果和部分功能,目的是由此来了解高级UI的原理。
- 编辑器集成,所见即所得 - 本地化、数据绑定、委托、事件 - 支持所有平台 - 制作进行 1 次绘制调用的 UI - 随附完整的 C# 源代码 - 已广泛优化 - 专门团队支持 2020.1.5 - NEW: You can now specify per-symbol ...
在场景视图中看到的就是在游戏视图中得到的(所见即所得)。 基于组件的、模块化的特性:要让你的界面控件做什么,只需为其附加相应的行为,而不需要编码。 全面支持iOS/Android和Flash。 灵活的事件系统。 可以让...
在NGUI API中,我们首先会接触到UIRoot,它是NGUI的核心组件,负责管理所有的UI层以及它们的渲染顺序。UIRoot可以设置不同的屏幕分辨率适应策略,确保UI在不同设备上正确显示。接着是Widget,它是所有UI元素的基础类...
《NGUI 3.6.4:打造高效Unity3D移动界面的利器》 NGUI,全称为Next-Gen UI,是Unity3D游戏引擎中的一款广泛应用的界面系统插件,尤其在移动游戏开发领域备受青睐。其3.6.4版本作为官方发布的最新版,不仅集成了之前...
在场景视图中看到的就是在游戏视图中得到的(所见即所得)。 基于组件的、模块化的特性:要让你的界面控件做什么,只需为其附加相应的行为,而不需要编码。 全面支持iOS/Android和Flash。 灵活的事件系统。...
NGUI Next-Gen UI是一款功能强大、灵活性高的UI插件,是当前最新版本的NGUI插件。它可以覆盖Unity的多个版本,包括Unity 5、Unity 2017和Unity 2018等。与其他UI插件相比,NGUI Next-Gen UI具有高效的性能和优秀的...
NGUI3.7.3unitypackage 是 NGUI 的一个特定版本,即3.7.3版的资源包。这个版本包含了对 NGUI 系统的更新和改进,旨在提升性能、优化用户体验以及增加新的功能。 NGUI 的核心特性包括: 1. **Widget 系统**:NGUI ...
NGUI 是一款非常强大的 UI 系统和事件通知框架。...- 编辑器集成,所见即所得 - 本地化、数据绑定、委托、事件 - 支持所有平台 - 制作进行 1 次绘制调用的 UI - 随附完整的 C# 源代码 - 已广泛优化 - 专门团队支持
在场景视图中看到的就是在游戏视图中得到的(所见即所得)。 基于组件的、模块化的特性:要让你的界面控件做什么,只需为其附加相应的行为,而不需要编码。 全面支持iOS/Android和Flash。 灵活的事件系统。...
《NGUI v3.12.1:2018年Unity UI系统深度解析》 NGUI,全称为Next-Gen UI,是一款专为Unity引擎设计的用户界面系统。在2018年,它发布了v3.12.1的最新版本,此更新旨在优化用户体验,提升性能,并增加新功能。作为...
NGUI 是一个针对 Unity 游戏引擎的用户界面(UI)系统,专为游戏开发者设计,提供了一套高效、易用且功能丰富的UI组件。在3.11.2版本中,NGUI 进一步提升了性能和用户体验,遵循了“Keep It Simple, Stupid”(KISS...
《NGUI 3.7.0:Unity开发插件的深度解析与应用指南》 NGUI,全称为Next-Gen User Interface,是一款专为Unity3D游戏引擎设计的UI开发插件,它提供了丰富的界面元素和强大的交互功能,极大地简化了开发者在创建用户...
在本篇NGUI教程中,我们将深入探讨Unity游戏引擎中的NGUI插件使用方法。NGUI是一个为Unity制作高效、现代UI的工具包,它提供了一套用户界面系统,使开发者能够创建复杂的用户界面,而不必担心性能问题。本教程将详细...
本包中共有六个版本的NGUI,大家可以自己选择版本。 NGUI Next-Gen UI 3.6.0.unitypackage NGUI Next-Gen UI 3.12.1(u5.6.5).unitypackage NGUI Next-Gen UI 2019.3.0.unitypackage NGUI Next-Gen UI v2018.3.0....