NGUI 屏幕适配(2D UI)

Wesley13
• 阅读 585

博客内容基于 NGUI.3.11.2,讲述 NGUI 2D UI 。

设备无关坐标

以 OpenGL 为例,经过各种图形阶段(stage)后,几何形状最终被转换成像素显示在屏幕上。设备屏幕坐标以像素为单位,是设备相关的。不同设备分辨率不一样,即使同一设备,不同窗口的大小也不一样。在不关注具体设备的情况下绘制几何形状,则需要设备无关坐标系统。OpenGL 标准化设备坐标空间(NDC)在 X 轴、Y 轴,Z 轴上是 [-1, 1] 。举个例子,不做任何坐标变换(transform)和视口变换(viewport),对于顶点坐标(-1.0f, -1.0f, 0.0f)、(1.0f, -1.0f, 0.0f)、(1.0f, 1.0f, 0.0f)、(-1.0f, 1.0f, 0.0f),调用 glDrawArrays 并指定 GL_TRIANGLE_FAN 绘制的长方形覆盖整个窗口。

Unity 提供的坐标单位(设置 Transform 组件各属性值,如 position)就是设备无关的。设置 Camera.orthographic = true 使用 2D 正交摄像机,假设屏幕宽高比为 aspect ,则正交摄像机 XY 平面矩形宽高分别为 cam.orthographicSize * 2.0f * apsectcam.orthographicSize * 2.0f

NGUI 2D UI 像素单位

使用菜单 NGUI|Create|2D UI 创建 2D UI 。具体的创建函数 NGUITools.CreateUI 有如下设置 2D UI 部分代码,并且摄像机处于(0, 0, 0)。则可以计算渲染矩形的范围(先忽略 Z 轴)。

cam.orthographic = true;
cam.orthographicSize = 1;
cam.nearClipPlane = -10;
cam.farClipPlane = 10;

假设屏幕宽高比 aspect = 1.778f 。由于摄像机处于中心,则渲染矩形的左下角世界坐标是(-1.778f, -1.0f),右上角世界坐标是(1.778f, 1.0f)。这个坐标范围与设备无关,处于这个范围内便可以被渲染。

再想一下,如果在这个坐标范围内再封装一层并且把 GameObject 的 Transform.localScale 属性设置为(1.0f, 1.0f, 1.0f)(避免受到被此属性影响),然后坐标轴都缩小 360 倍,为了使得 GameObject 仍能被渲染,则 GameObject 的坐标值要被放大 360 倍。则现在左下角坐标变成了(-640.0f, -360.0f),右上角为(640.0f, 360.0f),现在坐标范围变成了 1280 x 720,此时一张 1280 x 720 的图片则可以显示在整个窗口中。于是形成了以像素为单位的新的坐标系统,这样会比之前的设备无关坐标范围更容易理解和使用。

其实 NGUI 像素单位的本质就是选择一个缩放因子(factor)对坐标进行缩小,这样 GameObject 实际的坐标值和大小值便可以放大相应的倍数,便于使用。最终渲染时会再乘以 factor 得到实际的坐标值或者大小值。NGUI 是根据高度计算缩放因子的,上例中高度是 2.0f 且设置 aspect = 1.778f,缩放因子是 factor = 2.0f / 720,于是构造了 1280 x 720 虚拟分辨率。若设备屏幕大一些,则 2.0f 对应的高度便会大一些,图像也就显示的大一些,反之便显示的小一些。

屏幕适配

UIRoot 与缩放因子

前面介绍了缩放因子,那么 NGUI 中是如何处理的呢?使用菜单创建 UI 时,会创建两个 GameObject ,UIRootUIPanel添加到 UI Root 而 UICamera 添加到 Camera 。UIRoot 处理缩放逻辑,UIPanel 管理渲染,UICamera 管理消息处理。一个 NGUI 渲染实例在 Unity Inspector 面板中以树形结构具体表现,UIRoot 所在的 GameObject 为根节点(是其余所有 GameObject 直接或间接的 parent),其余 GameObject 都作为此根节点的直接或者间接子节点。多个 NGUI 渲染示例(UIRoot)不能有交集,不然其中一个作为另一个的 child 会导致缩放逻辑会出错。

缩放因子的计算与应用在 UIRoot.UpdateScale 函数中。其中两行如下。

float size = 2f / calcActiveHeight;
mTrans.localScale = new Vector3(size, size, size);

size 就是前面提到的缩放因子。calcActiveHeight 是考虑屏幕适配后计算的高度值(以像素为单位)。然后把 size 赋值给 localScale 。由于 UIRoot 附加到根节点,所以其他的所有 GameObject 都会受到此缩放因子的影响。

这里 2f 其实就是 2 * Camara.orthographicSize,由上面可知 orthographicSize 被设置成了 1f 。

不同的分辨率

实际开发时会选择一个标准分辨率,如 1280 x 720(aspect 为 16:9),并以此为标准设计美术资源,比如游戏背景图片为 1280 x 720 。对于手游,不同设备的分辨率也不同,屏幕适配就是为了把以开发时选择的标准分辨率为基准创建的游戏画面,更好的在不同的设备上显示。

假设 calcActiveHeight 返回的值就是 720(Scaling.Constrained 和 Constraint.FitHeight)。

  • 若设备屏幕宽高比是 16:9 则能完毕适配。实际高度是 2.0f,则根据宽高比计算宽度为 3.556f 。假如有一张显示在中心的图片,大小为 1280 x 720 像素,乘以缩放因子(2.0f / 720)后刚好与实际的宽度和高度一致,图片正常全屏显示。注意只要是宽高比是 16:9,不管设备是 1280 x 720 还是 640 x 320 或其他分辨率,都是能完美适配的。因为 Unity 会自动把宽度 3.556f 和 高度 2.0f 与设备窗口的宽和高映射,这就是设备无关坐标。
  • 若设备屏幕宽高比不是 16:9 比如 1024 x 768(4:3),则一张 1280 x 720 显示在中心的图片不能完全适配。实际高度是 2.0f, 宽度为 2.667f 。而缩放因子为 2.0f / 720,图片的宽和高乘以缩放因子得到宽和高分别为 3.556f 和 2.0f,和实际的宽度 2.667f 相比,图片更宽,所以图片会超出部分屏幕。如下图两种分辨率对比。
  • NGUI 提供了多种屏幕适配策略由 UIRoot.scalingStyleUIRoot.constraint 指定,这里就不深入分析了。基本的策略有如下。
  • 简单的完全适配宽度或者高度。当不能完美适配时,图片会在垂直或者水平方向超出。
  • 比较标准宽高比和设备的宽高比,来选择在水平还是垂直方向完全匹配。若不能完美适配时,图片会全部显示,只是会有一个方向会多出间隙。
  • NGUI 也提供了锚点功能。通过结合屏幕适配测试和锚点,来使图像更好的跨设备显示。

NGUI 屏幕适配(2D UI)

2D UI 还是 3D UI

假设标准分辨率是 1280 x 720,不使用锚点且 UIRoot 使用 Scaling.ConstrainedConstraint.FitHeight。有上图可知在 1024 x 768 的设备上,图片在水平方向会超出。若选择 Constraint.FitWidth 则 2D UI 可以完全显示图片,垂直方向会有一些间隙,但是 3D UI 不能。

具体的原因先不分析,简要介绍一下,2D UI 中缩放因子影响渲染矩形平面的缩放。而 3D UI 中缩放因子除了影响渲染平面,还影响渲染平面到摄像机的距离,所以会造成 UIRoot 选择不同的缩放策略而没有效果。

所以我觉得,当需要 3D 效果时才使用 NGUI 3D UI,默认就使用 2D UI 即可。

点赞
收藏
评论区
推荐文章
blmius blmius
2年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Wesley13 Wesley13
2年前
Android动态Java代码调整window大小
Android调整window大小举一个例子,设置当前的APP所需要的屏幕高度为设备高度的一半:WindowwindowgetActivity().getWindow();WindowManager.LayoutParamswindowLayoutParamswindow.ge
Stella981 Stella981
2年前
Android app ADB命令
\查看设备adbdevicesps这个命令是查看当前连接的设备,连接到计算机的android设备或者模拟器将会列出显示若有多台安卓设备,可以通过在adb后面加上s<设备id对指定设备进行装包、卸载等操作\启动adbadbstartserver\关闭adbadbkillserver\安装软件
Easter79 Easter79
2年前
SwiftCommon之Device设备信息
概述获取设备的信息,比如系统版本号、屏幕高宽等。在IOS中,我们通过访问UIDevice类,获取设备信息;通过UIScreen获取屏幕的信息。SCDeviceimportFoundationimportUIKitpublicclassSCDevice{
Wesley13 Wesley13
2年前
(绝对有用)iOS获取UUID,并使用keychain存储
UDID被弃用,使用UUID来作为设备的唯一标识。获取到UUID后,如果用NSUserDefaults存储,当程序被卸载后重装时,再获得的UUID和之前就不同了。使用keychain存储可以保证程序卸载重装时,UUID不变。但当刷机或者升级系统后,UUID还是会改变的。但这仍是目前为止最佳的解决办法了,如果有更好的解决办法,欢迎留言。(我整理的解决办法的参
Stella981 Stella981
2年前
Android蓝牙连接汽车OBD设备
//设备连接public class BluetoothConnect implements Runnable {    private static final UUID CONNECT_UUID  UUID.fromString("0000110100001000800000805F9B34FB");
Stella981 Stella981
2年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Stella981 Stella981
2年前
Android 图形系统概述
Androidframework为2D和3D提供了各种各样的图形渲染APIs来与设备制造商的图形驱动实现交互,因此对于那些API在上层如何工作有一个好的理解非常重要。这一页介绍驱动基于其构建的图形硬件抽象层(HAL)。应用程序开发者以两种方式将图像绘制到屏幕上:通过Canvas或OpenGL。参考 系统级图形架构(https
暗箭伤人 暗箭伤人
7个月前
【www.ithunter.club】 20230922下午
不容易的2023年,我们一起努力【www.ithunter.club】(2023092208:00:00.8872062023092216:00:00.887206)1.人事招聘专员数名(可选远程或入职)2.招聘向坐标东京Yahoo、Shift、L
燕青 燕青
5个月前
Macos屏幕分辨率修改工具:SwitchResX for Mac 破解版
是一款非常实用的显示分辨率切换工具,可以帮助用户在Mac上轻松地切换显示分辨率。它支持多种屏幕分辨率和DPI设置,允许用户根据需要自定义显示分辨率和缩放比例。SwitchResXMac支持所有MacBook和iMac系列,可以快速切换不同设备的分辨率和DP