我们先从一个实际的需求出发。
需求描述:3D游戏中的怪物死亡会随机掉落一个宝箱,当有宝箱掉落时需要实现一个 UI 宝箱效果,从怪物当时所在的屏幕坐标处飞到游戏的宝箱 UI 上,然后宝箱计数加1。
解决思路:
宝箱的动画属于 UI层 的东西,需要在 UI 坐标系上进行;
需要将怪物的 3D世界坐标 转换成 2D的UI坐标。
为了解决以上问题,我们先来了解一下UGUI中涉及到的坐标系,我把这些坐标系总结为三个类型:屏幕坐标系,UI逻辑坐标系和UI世界坐标系。下面来逐一介绍。
屏幕坐标系是和屏幕分辨率一致的坐标系。
屏幕坐标系以屏幕的左下角为远点,屏幕右上角的坐标是(Screen.width, Screen.height)。示意图如下图所示:
UI逻辑坐标系是我们在设计UI界面的时候的参考坐标系。
参考坐标系一般对当前流行的手机的分辨率保持一致,例如当前流行的是 iPhone6s 的 2000*1125,则美术在制作界面示意图的时候就需要参考这个分辨率进行制作,保证在当前主流机型上有最佳的显示效果。
uGUI中坐标系的起点和屏幕坐标系是相同的,界面的左下角是原点,size是通过 Canvas 和 Canvas Scalar 组件来确定的。如下图所示。
没有 Canvas Scalar 组件
此时 UI逻辑坐标系 == 屏幕坐标系。一般不采用此种用法,除非只想针对一种分辨率设备来做UI或者有自己的屏幕适配方案。
有Canvas Scalar
顾名思义,Canvas Scalar 使用 RectTransform 的 localScale 来使得 UI逻辑坐标 * RectTranform.localScale == 屏幕坐标。
Canvas Scalar 有三种 UIScaleMode:
Constant Pixel Size: 不论屏幕分辨率怎么变化,UI元素例如按钮在不同分辨率上占据的像素大小相同。这种情况下需要使用者来手动设置 ScaleFactor 来设置 localScale。
Scale With Screen Size:根据实际屏幕的宽高来设置缩放
这种方式是根据屏幕的实际比例来调整 Size 和 Scale,调整Size保证和设计时一致,调整 Scale 是为了和屏幕坐标系一致。
这里需要填入一个 Reference Resolution,就是我们制作 UI 的时候的参考屏幕大小,这里为了示例,我设置成了 800*600,而屏幕分辨率是 960*640。
其中 ScreenMatchMode 又分为三种模式:
Expand:尽量减小缩放 scale = Min(screenSize.x / ReferenceResolution.x, screenSize.y / ReferenceResolution.y)
Shrink:尽量放大缩放 scale = Max(screenSize.x / ReferenceResolution.x, screenSize.y / ReferenceResolution.y)
MatchWidthOrHeight:根据屏幕的宽或高或者之间的一个值来进行缩放
scaleWidth = screenSize.x / ReferenceResolution.x;
scaleHeight = screenSize.y / ReferenceResolution.y;
scale = scaleWidth * (1 - scaleWithWidthOrHeight) + scaleHeight * scaleWithWidthOrHeight
上面三种适配方式的结果如下:
一般在进行屏幕适配的时候会选择 MatchWidthOrHeight 来进行适配。
Scale with Physical Size:在像素上和设计时类似 scale = Screen.dpi / TargetDPI
在确定了 scale 之后,UI逻辑坐标系的大小 = 屏幕坐标系大小/scale,这样子可以保证UI逻辑坐标系的宽高比(Aspect Ratio)和屏幕坐标系的宽高比相同。
UI世界坐标系是指通过 RectTransfrom.position 获取的坐标值。这个是由 Canvas 组件的 RenderMode 来决定的:
Screen Space - Overlay:则 UI 坐标系和屏幕坐标系完全一致,RectTransform.position 代表的就是屏幕坐标位置。
Screen Space - Camera:此时 Canvas 需要指定一个 Camera。根据Camera的 Projection,则又有不同。
Perspective: UI 坐标区域由 Camera 到 Canvas 的距离决定,不细述。
Orhographic: Camera.size 决定了 整个屏幕的 UI 坐标区域。
此时通过 Canvas 上的 RectTransfrom 的 localScale 来将 屏幕坐标规约到 UI 坐标区域。
此时如果需要获取需要获取一个UI的屏幕坐标,可以通过下述代码来获得:
World Space:此时没有 UI 坐标系这个概念了,不细述。
Overlay的 RenderMode 该如何选择?
如果UI一致显示界面的最高层,那么使用 Screen-Space Overlay 模式即可;如果需要多个 Canvas 可以通过 Sort Order 来进行分成,Sort Order 越大显示越靠前;
如果UI中间需要嵌入一个3D物体,例如背景UI在最底层,人物 avatar 显示在中间层,上层显示其他 UI界面,则使用 Screen-Space Camera,此时需要多个 Camera ,然后通过 Camera 的 depth 来控制显示的次序,depth 越大越后渲染,越靠前。另外各个 Camera 的 ClearFlags 需要正确设置,背景的设置为 Solid Color,负责擦屏幕;之后的设置为 Depth Only 即可;
如果 UI 界面需要显示在世界中,则需要使用 World Space,例如在 VR 场景中。
Canvas Scalar的 UIScaleMode 该怎么选择?怎么可以让UI可以适配所有设备?
这里介绍一下个人使用经验,因为其他用得不多,优点还不知道:
UIScaleMode 设置成 ScaleWithScreenSize;
Reference Resolution 设置成当前最主流机型的分辨率,同时告诉美术这个就是参照的大小;
Match 设置成0或者1,设置成0的时候就是按照宽度适配,则以后宽度不需要考虑适配,高度上进行适配,此时告诉UI在设计UI的时候高度上一定要设置成高度可以调节的;反之亦可。一般建议横屏设置成宽度不变,竖屏设置成高度不变。
什么时候用 UI 逻辑坐标系,什么时候用 UI世界坐标系?
在游戏制作过程中拼界面的时候用 UI 逻辑坐标系,这个时候需要使用 RectTransform 提供的 pivot, anchorPos等进行UI的对齐和大小设置;
在使用程序控制UI进行动画的时候最好使用 UI世界坐标系。
代码工程放在github[https://github.com/tnqiang/UI_Flow_Effect]上,代码解释:
第一步需要将 source 3D 物体从世界坐标转换到屏幕坐标,这个需要3D世界摄像机才能取得;
第二步需要将这个屏幕坐标转换到 UI 世界的世界坐标中,这个需要看 Camara 的 RenderMode 是否是 Overlay,如果是则直接是世界坐标,否则需要UI的摄像机来帮助。
uGUI源代码之CanvasScaler[https://bitbucket.org/Unity-Technologies/ui/src/0155c39e05ca5d7dcc97d9974256ef83bc122586/UnityEngine.UI/UI/Core/Layout/CanvasScaler.cs?at=5.2&fileviewer=file-view-default]
联系客服