前段时间整理的一篇关于unity ui开发的文章,被推荐上了csdn首页,对于刚刚写文字的我来说,是莫大的鼓励,让我干劲十足,写出更多有质量的文字。
写在前面
屏幕适配是每个手机应用和游戏都会解决的问题,当然在开发的过程中会遇到各种各样的坑,这次,我们就来讨论一下unity项目中的屏幕适配吧!
目录
- 屏幕适配的分类
- 哪些内容需要适配
- unity中常见的适配方式
- 游戏内容适配
- NGUI的适配方案
- UGUI的适配方案
屏幕适配的分类
说到屏幕适配的分类啊,也许会有所疑问,屏幕适配还能分类?细致分析一下,可以分为两大类:分辨率适配和宽高比适配。
分辨率适配
首先得知道分辨率是什么?分辨率是屏幕显示图像的紧密度,是指显示器能显示的像素有多少。屏幕上的点、线、面都是由像素组成的,分辨率越高,同样大小的屏幕能显示的像素越多,画面就越精细。现在PC上分辨率大多是 1920 * 1080,我们看的视频很多高清版本就是 1080p 的。
既然分辨率是屏幕的一项指标,那么手机上当然也会用到,现在智能手机市场有那么多产品,有多个厂家生产,并且有多个价位,所以手机屏幕分辨率肯定各不相同(虽然屏幕分辨率一般比较固定的几个)。那么分辨率适配是每个应用、游戏都应该做的。宽高比适配
这个很好理解,每个手机大小各不相同,宽高比也会有多种啦,适配宽高比当然也是必须要做的喽。
当下移动设备主流分辨率及宽高比:
iOS设备的分辨率主要有:
Android设备的分辨率则相对纷杂,主流的分辨率有:
哪些内容需要适配
- User Interface
游戏UI需要适配,这是无所质疑的,包括一般的手机应用程序,都需要这个步骤。如果再细分一下,还分为位置适配和大小适配
位置适配:分辨率会影响UI在屏幕中显示的位置,比如在800 * 600分辨率的屏幕上,button1在正中央位置,坐标为(400, 300),但是如果放在1366 * 768分辨率屏幕上位置就会靠左边一些,这样会严重影响UI布局的美观。
大小适配:同样的例子,在800 * 600分辨率的屏幕上,button1的大小为50*20像素,但是到了分辨率高的屏幕上,button1就变得很小了,影响美观,影响用户正常使用。
- 游戏内容
一般的应用开发,用户看到的只有UI,但在游戏中,除了UI,还有游戏内容。而游戏内容是什么呢?
举个例子:
比如在2D游戏中,除了那些可以交互的按钮滚动,在二维场景中的背景、物件、NPC等,都属于游戏内容。在进行宽高比适配的时候,难免会按照宽度或高度做一些裁剪,如果不进行处理,有些游戏内容就会看不到。
在3D游戏中,场景的大小是固定的,当相机照射的宽高比因为适配屏幕宽高比变化时,就可能“穿帮”,很有可能看到黑边。
在unity中不管2D游戏还是3D游戏,分辨率大小不会影响游戏内容显示,屏幕宽高比会影响游戏内容显示。
注意:在unity编辑器中,game视图是默认的视口,并且编辑器下改变游戏宽高比,unity会自动把相机宽高比调整到适配的宽高比。
unity中常见的适配方式
- Camera组件
Projection:投影类型
Prespective为透视投影
Field of View:相机的张角,它决定相机照射的范围。
Clipping Planes:近裁剪面和远裁剪面
Viewport Rect:视口大小,取值为0 ~ 1之间
Orthographic为平行投影
与透视投影不同的是size属性,它用来调整摄像机的大小
orthographicSize:等于相机高度的一半
注意一下,unity中的单位和像素之间有一个转换关系,叫做Pixels To Units
默认为100,unity中一个单位表示图片的100个像素。如果游戏屏幕高为800像素,那么换算后高度为 800 / 100 / 2 = 4。
unity没有直接设置摄像机宽度的属性,也没有获取摄像机宽度的接口,但可以通过高度和宽高比计算出来。那么计算宽度如下:
cameraWidth = camera.orthographicSize * 2 * camera.aspect
换算成像素:cameraWidth * 100
cameraHeight = camera.orthographicSize * 2
换算成像素:cameraHeight * 100
相机的宽高比是unity自动设置为当前屏幕宽高比的,所以camera.aspect不需要自己设置。
-
缩放
在Transform组件上,可以设置控制物体每个方向上的缩放比例。
锚点(相对位置)
目前NGUI,UGUI都有类似的功能,稍后再讨论。
游戏内容适配
游戏内容可以分为两类
有效内容:游戏中一定需要显示在屏幕上的内容
实际内容:包括有效内容和为了适配、或其它目的增加的内容。
3D游戏中把要么场景做得比正常显示时更大一些;要么显示天空盒子。
2D游戏中也是把背景做得大一些,尽可能让游戏不出现黑边。
我们的开发一般都会选择在一个固定的设计分辨率上进行,比如常用的iOS竖屏游戏设计分辨率640*960,我们就以这个设计分辨率为例。通常情况下,设计分辨率尺寸就是我们游戏有效内容的尺寸。
orthographicSize设置为4.8,就可以让游戏内容铺满屏幕
这里有一篇文章,里面详细讲了unity 2D游戏的屏幕适配。
NGUI适配方案
-
UIRoot
NGUI中每一个UI都是以UIRoot作为根节点,该组件完成了NGUI大体上的适配功能。
UIRoot的几种缩放方式:
Flexible:
在该模式下,下面的UI都是以像素为基础,100像素的物体无论在多少分辨率上都是100像素,这就意味着,100像素在分辨率低的屏幕上可能显示正常,但是在高分辨率上就会显得很小。
在该模式下,UIRoot的属性如下:
Minimum Height:设置为725时,当屏幕高度小于725时,在该屏幕上显示的样子和开发时一致。
Maximum Height:设置为1024时,当屏幕高度大于1024时,在该屏幕上显示的样子和开发时一致。
Shrink Portrait UI:当是竖屏状态时,按宽度来适配。
Adjust by DPI:使用dpi做适配计算。
补充一下一些关于屏幕的基本概念
dip:设备无关像素
dp:就是dip
px:像素
dpi:像素密度,单位面积上有多少个像素点
分辨率:宽高两个方向上的像素点数,如800*600
屏幕尺寸:屏幕对角线长度
屏幕比例:宽高比
详细的转换关系,去这里看看。
开发时的布局:
改变Game视图大小:
改变Game视图大小,高度大于725,小于1024,(按刚刚的截图中设置的值测试的):
在高度在Minimum和Maximum之间时,UIRoot就不会对下面的UI缩放了,开发时有多少像素在高分辨下也只有那么点像素,所以看起来就变小了。
这个Minimum和Maximum Height用于你对实际的屏幕尺寸进行限制,如果实际的屏幕尺寸小于Minimum,那么就相当于设置了“Constrained”模式、Manual Height值设为Minimum的时候一样,同理,如果屏幕尺寸超过了Maximum,那也相当于设置了“Constrained”模式、Manual Height值设为Maximum的时候一样。
以上是在Flexible模式的关于分辨率的适配,还有一个是宽高比适配,分两种情况:
当高大于宽的是,也就是竖屏状态时
两边被截了
只需要勾上Shrink Portrait UI,就能按照宽度来适配了(因为默认横屏状态,并且默认按高度适配,所以在看这段源码的时候,它里面的计算是宽高颠倒的):
当宽大于高时,也就是横屏状态时:就需要自己来根据宽度来调整缩放。
- 动态的改变适配的高度
public class NewBehaviourScript : MonoBehaviour {
public int ManualWidth = 1280;
public int ManualHeight = 720;
void Awake () {
UIRoot uiRoot = gameObject.GetComponent<UIRoot>();
if (uiRoot != null)
{
if (System.Convert.ToSingle(Screen.height) / Screen.width > System.Convert.ToSingle(ManualHeight) / ManualWidth)
uiRoot.minimumHeight = Mathf.RoundToInt(System.Convert.ToSingle(ManualWidth) / Screen.width * Screen.height);
else
uiRoot.minimumHeight = ManualHeight;
}
}
}
-
利用相机的camera.orthographicSize
需要知道orthographicSize表示的是相机高度的一半,前面已经讲清楚了。我在16 : 9屏幕下开发,并且设置camera.orthographicSize为1,把Minimum和Maximum设置为相同
,然后把下面脚本挂在UI相机上:
public class NewBehaviourScript : MonoBehaviour {
void Awake ()
{
camera.orthographicSize *= 16.0f / 9 / ((float)Screen.width / Screen.height);
}
}
16:9屏幕上正常:
不加上述脚本,在5:4屏幕上,两边被裁剪了:
加上上述脚本,在5:4屏幕上就正常了,按照宽度适配:
Constrained:
该模式下,屏幕按照尺寸比例来适配,不管实际屏幕有多大,NGUI都会通过合适的缩放来适配屏幕。这样在高分辨率上显示的UI就会被放大,有可能会模糊。
Content Width:按照该宽度值适配屏幕
Content Height:按照该高度值适配屏幕
Fit选项表示已哪个值做适配。这两个值可以认为是事先设定好的屏幕初始大小和比例。源码中Fit选项的枚举值:
public enum Constraint
{
Fit,
Fill,
FitWidth,
FitHeight,
}
如果Fit都没勾选(Constraint.Fill)
当适配宽高比小于实际宽高比时,就会按照宽度适配,反之按照高度适配。该情况下可以保证显示结果永远没有黑边,但上下或者左右两边可能会被裁剪。如果勾选了Width(Constraint.FitWidth)
那么就会在屏幕比例发生变化时,按照宽度来适配。例如开发时用16:9屏幕,运行在5:4屏幕上,宽度适配,计算公式为 activeHeight = manualWidth / (screen.x / screen.y),16 / ( 5 / 4 ) = 12.8 > 9,这样显示宽度就全部显示进来了,但是高度上就会出现黑边,所以这种情况下要想办法解决黑边问题。如果勾选了Height(Constraint.FitHeight)
那么就会在屏幕比例发生变化时,按照宽度来适配。activeWidth = manualHeight * (screen.x / screen.y),9 * ( 5 / 4 ) = 11.25 < 16,这样高度全部显示进来,但在宽度上两边被裁剪掉了,显然这样更不合适了。如果两个都勾选了(Constraint.Fit)
当适配宽高比大于实际宽高比时,就会按照宽度适配,反之按照高度适配。该情况下可以保证显示开发时能见到的所有内容,但是可能上下或左右会出现黑边。
下面是UIRoot.cs源码:
可以清楚的看到NGUI是怎么利用这些值进行计算高度的。
Constrained On Mobiles
前两种模式的组合,在PC和Mac等桌面设备上用Flexible模式, 在移动设备上Constrained模式。
- UIAnchor or UIStretch
作为NGUI中一个组件,但之前做的项目里面好像怎么用,可以把它看做对一个UI树中的局部进行控制。它们在处理细节上很相似,都是先计算参照对象(这个参照对象由Insprector的Container指定,如果没有选择,就是Camera)的大小Rect,然后根据参数UIAnchor(Side,relativeOffset,pixelOffset),UIStretch(Style,relativeSize,initialSize,borderPadding)进行调整,最后设置对应的属性,只不过UIAnchor设置的是transform.position,UIStretch设置的是(width,height)或clipRange等。
UIAnchor组件的视图:
Container:指定Anchor的参照点,如果没有选择,则以Camera的pixelRect的区域为参照面 。
Side:锚点位置,有八个位置可选。
Relative Offset:相对于Camera,或Container的百分比偏移 。
Pixel Offset:像素的偏移。
UIStretch组件的视图:
大致和UIAnchor差不多,Style属性有些改动如下
Horizontal:横向拉伸。
Vertical:纵向拉伸。
Both:双向拉伸,但xy方向上的拉伸比例不同。
BasedOnHeight:双向拉伸,但xy方向上的拉伸比例相同,且比例基于height。
FillKeepingRatio:双向拉伸,但xy方向上的拉伸比例相同,比例基于较大者。
FitInternalKeepingRatio:双向拉伸,但xy方向上的比例相同,比例基于较小者。
具体的可以自己研究一下,但是不建议用这个两货,折腾了一下感觉太麻烦,而且根本没必要啊,因为UIRoot已经做了大部分的适配了,那些局部细节上的调整完全可以用UIRect所管理的Anchor来实现,它不是单独的组件,比这两简单多了,下面就来聊聊它。
- UIRect的Anchor
首先得了解一些UIRect,这里不详细聊它,后面会整理一篇分析NGUI底层的文章,里面有详细说它。简单介绍一下,从NGUI控件的继承结构上,UIRect是所有weight和panel的基类,管理着rect和anchor,计算、生成,是一个抽象类。
拿UISprite举例:
Type:三种类型,使用锚点、基本控制、完全控制。
Execute:设置在什么时候执行锚点适配。
Target:参考物体。
Left、Right、Bottom、Top:该控件上下左右边。
比如,你想某个按钮在任何尺寸屏幕上都停留在屏幕上的左边,可以如下:
16:9屏幕上
锚点设置如下:UISprite的左右边界都参考target的左边
然后5:4屏幕上,UISprite依然在屏幕的左边了
当然其它的weight都可以设置锚点,可以这么说,凡事继承自UIRect的组件都可以使用该锚点。
UGUI适配方案
终于把NGUI适配说完了,对于UGUI目前没有深入了解,在场景视图中可以拖拽锚点,设置锚点区域,感觉挺简单的,粗略做个笔记。
- Canvas Scaler:画布比例缩放,从整体上对UI进行适配控制,和UIRoot有异曲同工之妙,很多参数名字不一样,但意思一样。
ConstantPixelSize:按像素适配
Constant Pixel Size:保持UI元素大小不变,无论屏幕尺寸如何变化,所占像素不变。
Scale Factor:保持大小的比例 。原图100x100 原始大小1=100x100 原来的2倍大 2=200x200
Reference Pixels Per Unity: 100 Unity里的1单位大小代表100像素
ScaleWithScreenSize:按比例适配
Scale With Screen Size:UI元素大小跟随屏幕分辨率的大小变化而变化。
Reference Resolution:参考分辨率。
Screen Match Mode:
Match Width Or Height:根据参考分辨率的高或宽,来缩放UI元素。
Expland:分辨率设置不会小于Canvas设置的分辨率。
Shrink:分辨率不会大于Canvas设置的分辨率。
Constant Physical Size:按屏幕物理大小适配
根据屏幕的PPI信息和ConstantPhysicalSize本身的配置信息,得出一个“合适”的scaleFactor,以达到UI在不同PPI设备上的最终大小都是一致的。
- 锚点
UGUI中锚点有多种“形态”,当锚点是一个点时,表示该UI大小不变,位置会随参考点改变。当锚点是一个矩形区域时,UI的大小就会随该参考区域改变,当然非常灵活,锚点矩形的大小可以随意设置,甚至可以在某个方向长度为0。
写在最后
以上就是屏幕适配的所有内容,主要介绍了屏幕适配的分类:分辨率适配和宽高比适配,按内容又分为游戏UI适配和游戏内容适配,并给出一些适配方法。然后重点讲了NGUI的适配方法,简单介绍了UGUI,总的来说UGUI和NGUI适配的方案有很多相似的地方,适配的大致方向就是按像素、按比例缩放对全局适配,用锚点来做精细的控制。对UGUI现在不是很熟,所以写的很简单,以后找时间在详细研究一下,再整理出来。