说说Unity3D里的各种坐标系
各位朋友大家好,欢迎关注我的博客。我是秦元培,博客地址是 http://qinyuanpei.com。今天我想和大家聊聊 Unity3D 中各种坐标系。自从 Unity 4.6 版本推出 uGUI 后,Unity3D 坐标系大家庭中便增加了 RectTransform 这个新成员。如果你不想被各种坐标系搞得晕头转向,那么请随我一起来梳理下 Unity3D 中各种各样的坐标系。
一、Unity3D 中有哪些坐标系?
坐标系这一概念最早由法国数学家笛卡尔提出,他由此开创了用代数方法研究几何图形的数学分支——解析几何。解析几何的基本思想是将几何图形抽象为点的运动轨迹,点成为组成图形的基本元素。而描述一个点的位置,首先需要建立合适的坐标系。下面我们来了解一下 Unity3D 中都有哪些坐标系。
目前,Unity3D 中的坐标系可分为以下四类:世界坐标、屏幕坐标、视口坐标和 GUI 坐标。下面对这四类坐标进行详细说明。
世界坐标
世界坐标是按照笛卡尔坐标系定义的绝对坐标系,其他各类坐标系均建立在世界坐标的基础之上。在二维平面内,任意一个点可以用二维坐标 (x, y) 表示;将这一概念延伸到三维空间,三维空间内任意一个点都可以用三维坐标 (x, y, z) 表示,这就是世界坐标的概念。
坐标系通常分为左手坐标系和右手坐标系,Unity3D 采用的是左手坐标系。在 Unity3D 中,我们可以使用 transform.position 来获取场景中一个物体的世界坐标。通常情况下,编辑器中的 Inspector 窗口以世界坐标来描述一个 3D 物体的位置,但当一个 3D 物体存在父物体时,它会以相对坐标来描述其位置。
屏幕坐标
屏幕坐标以像素为单位进行定义,其范围是一个矩形,左下角为 (0, 0),右上角为 (Screen.width, Screen.height)。屏幕坐标是一个 3D 坐标,Z 轴以相机的世界单位来衡量。屏幕坐标和相机之间满足 Screen.width = Camera.pixelWidth 和 Screen.height = Camera.pixelHeight 这两个条件。
例如,将相机正对着场景中的原点 (0, 0, 0),相机的 Z 轴分量为 -10,假设屏幕大小为 800X640,则此时原点转化为屏幕坐标后应该是 (400, 320, 10)。在 Unity3D 中,我们可以使用 WorldToScreenPoint 方法将一个世界坐标转换为屏幕坐标。
视口坐标
视口坐标是标准化后的屏幕坐标。标准化的概念类似于向量的标准化,例如一个向量 (x, y) 经过标准化后可以得到一个单位向量 (x’, y’)。视口坐标以 0 到 1 之间的数字来表示,其范围是一个矩形,左下角为 (0, 0),右上角为 (1, 1)。视口坐标也是一个 3D 坐标,Z 轴同样以相机的世界单位来衡量。
通过对比可以发现,视口坐标和屏幕坐标非常相似,大家可以对比着学习。同样以屏幕坐标中的例子进行转换,将相机正对着场景中的原点 (0, 0, 0),相机的 Z 轴分量为 -10,假设屏幕大小为 800X640,则此时原点转化为视口坐标后应该是 (0.5, 0.5, 10)。在 Unity3D 中,我们可以使用 WorldToViewportPoint 方法将一个世界坐标转换为视口坐标。
GUI 坐标
GUI 坐标是指通过 OnGUI 方法绘制 UI 时使用的坐标。该坐标系和屏幕坐标类似,同样以像素为单位进行定义,其范围是一个矩形,左上角为 (0, 0),右下角为 (Screen.width, Screen.height)。GUI 坐标是一个 2D 坐标(绝对坐标)。
由于 GUI 在早期文章中已有涉及,属于 Unity3D 中较为基础的内容,并且使用绝对坐标进行布局无法实现自适应,所以这部分内容不再展开讲解。UI 自适应的一个重要原则就是不要使用绝对坐标!
这里特别介绍一下 Unity 4.6 以后推出的全新 UI 支持:uGUI。在 uGUI 的 Screen Space 模式下,Unity3D 的编辑器以屏幕坐标来显示 UI 元素的位置;在 World Space 模式下,Unity3D 的编辑器以世界坐标来显示 UI 元素的位置。
经过深入研究发现,uGUI 的坐标本质上是特殊的屏幕坐标,因为 uGUI 的 Anchor 决定了该坐标系的原点,pivot 决定了元素本身坐标系的原点,这两个属性使得 uGUI 的坐标看起来比较复杂。RectTransform 组件继承自 Transform,所以它们的 position 和 localPosition 是等价的,都是世界坐标;anchoredPosition 是 UI 元素的屏幕坐标,在对 UI 元素进行操作时应该考虑使用这个坐标。
二、Unity3D 各种坐标系间的转换
虽然 Unity3D 提供了各种坐标系间的转换 API,但从实用性角度出发,这里提供一个 Unity3D 各种坐标系间转换的 FAQ。
1、屏幕坐标如何转换为世界坐标?
在 Unity3D 中,通过 camera.ScreenToWorldPoint(Vector3 v) 方法可以将一个屏幕坐标转化为世界坐标。其中,camera 是游戏场景中的场景相机。通过 Input.mousePosition 或者 Input.touches[0].position 可以获得鼠标或者单根手指的屏幕坐标。典型的应用场景包括第一人称射击游戏中子弹的发射和 RPG 游戏中点击地面移动角色。
2、世界坐标如何转换为屏幕坐标?
在 Unity3D 中,通过 camera.WorldToScreenPoint(Vector3 v) 方法可以将一个世界坐标转化为屏幕坐标。其中,camera 是游戏场景中的 UI 相机。例如,当需要将一个世界坐标转换到 NGUI 坐标时,可以使用场景相机将世界坐标转为屏幕坐标,然后再利用 UI 相机将屏幕坐标转换为世界坐标,最后再赋值给 NGUI 控件。典型的应用场景包括 NPC 头顶的文字显示和怪物头顶的血条显示。
3、屏幕坐标如何转换为视口坐标?
在 Unity3D 中,可以通过 camera.ScreenToViewportPoint() 方法将一个屏幕坐标转换为视口坐标,其中 camera 是游戏场景中的场景相机。由于视口坐标本质上是屏幕坐标的标准化坐标,所以理解起来并不复杂。
4、世界坐标如何转换为视口坐标?
在 Unity3D 中,可以通过 camera.WorldToViewportPoint() 方法将一个世界坐标转换为视口坐标,其中 camera 是游戏场景中的场景相机。同样,因为视口坐标是屏幕坐标的标准化坐标,所以这一转换也比较容易理解。
5、屏幕坐标如何转化为 GUI 坐标?
屏幕坐标和 GUI 坐标的根本差异在于坐标系原点的选定不同,屏幕坐标的原点在左下角,而 GUI 坐标的原点在左上角。因此,我们可以定义一个 ScreenToGUIPoint 方法:
private Vector2 ScreenToGUIPoint(Vector2 v)
{
return new Vector2(v.x, Screen.height - v.y);
}
6、GUI 坐标如何转换为屏幕坐标?
可以继续调用 ScreenToGUIPoint 方法来实现 GUI 坐标到屏幕坐标的转换。
7、屏幕坐标如何转换为 uGUI 坐标?
当接触到 uGUI 后,常常会遇到为 uGUI 控件坐标赋值的问题,此时有以下两种处理思路:
- 直接赋值法:直接把屏幕坐标赋值给
transform.position,这种方式在 ScreenSpace - Overlay 模式下没有问题,但在 ScreenSpace - Camera 模式下会出现因深度数值不正确而引发的问题,需要注意(并且需要确保被赋值的物体在 Canvas 的根节点下)。 - 安全转换法:通过
RectTransformUtility.ScreenPointToWorldPointInRectangle方法将屏幕坐标转化为世界坐标,然后再赋值给transform.position。这种方法更为保险,因为它在两种模式下都能稳定运行,并且可以和 uGUI 的事件系统完美融合。不过,关于是应该给transform.position赋值还是给anchoredPosition赋值,目前还存在一些疑问,希望有了解的朋友可以分享一下。
好了,今天的内容就是这样。如果对文章中的内容有不明白或者质疑的地方,欢迎在博客里留言。最近状态不太好,感觉文章写得有些虎头蛇尾,希望后续能调整过来。