Unity里面坐标转换
在 Unity 中,坐标转换是游戏开发的核心基础(如物体跟随、射线检测、UI 定位、相机交互等场景均需用到)。其核心是 基于不同“坐标空间”的相互转换,所有转换的本质都是通过「矩阵运算」(Unity 已封装 API,无需手动计算)实现点/向量在空间中的映射。
一、先明确:Unity 中的 4 大核心坐标空间
在转换前,必须先区分“空间”——不同空间的坐标值无直接可比性,需通过特定 API 转换。
| 坐标空间 | 定义(参考系) | 用途 | 关键特性 |
|---|---|---|---|
| 世界空间(World Space) | 整个游戏世界的固定原点(0,0,0) | 描述物体在世界中的绝对位置 | 所有物体共用同一参考系,坐标值全局唯一 |
| 局部空间(Local Space) | 物体自身的 Transform 组件(自身原点= pivot 中心点) | 描述子物体相对于父物体的位置、父子层级关系 | 子物体坐标随父物体移动/旋转而变化 |
| 相机空间(View Space) | 相机的自身坐标系(相机为原点,Z 轴朝前(Unity 中是 -Z 轴看向世界)) | 3D 物体转 2D 屏幕的中间步骤、射线检测 | 坐标值表示“物体相对于相机的位置” |
| 屏幕空间(Screen Space) | 屏幕像素坐标系(左下角为原点 (0,0),右上角为 (Screen.width, Screen.height)) | UI 定位、鼠标交互(如点击 3D 物体) | 只关心 2D 像素位置,Z 轴为“物体到相机的距离” |
补充:UI 专用的「Rect Transform 空间」(如 Canvas 的 Screen Space - Overlay 模式)本质是屏幕空间的延伸,原点可能在左上角(需看 Canvas 设置),下文单独说明。
二、核心坐标转换:API 与场景示例
Unity 提供了 Transform 和 Camera 类的封装 API,覆盖绝大多数转换场景,无需手动操作矩阵。以下是 最常用的 6 种转换,含代码示例和适用场景:
1. 局部空间 ↔ 世界空间(父子物体/自身位置转换)
最基础的转换,用于将子物体的局部位置转为世界位置,或反之。
(1)局部坐标 → 世界坐标
API:Transform.TransformPoint(Vector3 localPos)
- 作用:将“相对于当前物体 pivot 的局部坐标”,转换为世界坐标。
- 注意:会考虑当前物体的 位置、旋转、缩放(如果父物体有缩放,子物体的局部坐标会被缩放后再转世界)。
示例:子物体相对于父物体的局部位置是 (1,0,0),获取其世界位置:
// 父物体
Transform parent = GameObject.Find("Parent").transform;
// 子物体相对于父物体的局部坐标
Vector3 localPos = new Vector3(1, 0, 0);
// 转换为世界坐标
Vector3 worldPos = parent.TransformPoint(localPos);
Debug.Log("子物体世界位置:" + worldPos);
(2)世界坐标 → 局部坐标
API:Transform.InverseTransformPoint(Vector3 worldPos)
- 作用:将“世界坐标”转换为“相对于当前物体 pivot 的局部坐标”。
- 注意:同样会考虑当前物体的位置、旋转、缩放。
示例:已知世界坐标 (5,2,3),获取其相对于父物体的局部坐标:
Transform parent = GameObject.Find("Parent").transform;
Vector3 worldPos = new Vector3(5, 2, 3);
Vector3 localPos = parent.InverseTransformPoint(worldPos);
Debug.Log("相对于父物体的局部坐标:" + localPos);
特殊情况:自身局部位置与世界位置的直接获取
- 物体的
transform.localPosition:直接获取“相对于父物体”的局部坐标(无需转换)。 - 物体的
transform.position:直接获取“世界坐标”(无需转换)。 - 仅当需要“自定义局部点”(非自身 pivot 位置)转换时,才用
TransformPoint/InverseTransformPoint。
2. 世界空间 ↔ 相机空间(3D 物体相对相机位置)
相机空间以相机为原点,Z 轴指向世界的 -Z 方向(Unity 相机默认“看向自身 -Z 轴”),用于计算物体相对于相机的位置(如判断物体在相机前方/后方)。
(1)世界坐标 → 相机坐标
API:Camera.WorldToCameraPoint(Vector3 worldPos)
- 作用:将世界坐标转换为“相对于相机的坐标”。
- 特性:相机移动/旋转时,转换结果会同步变化;Z 值为“物体到相机的距离”(正值表示在相机前方)。
示例:判断物体是否在相机前方(Z > 0):
Camera mainCam = Camera.main;
Transform target = GameObject.Find("Target").transform;
// 世界坐标转相机坐标
Vector3 cameraPos = mainCam.WorldToCameraPoint(target.position);
if (cameraPos.z > 0)
{
Debug.Log("物体在相机前方");
}
else
{
Debug.Log("物体在相机后方");
}
(2)相机坐标 → 世界坐标
API:Camera.CameraToWorldPoint(Vector3 cameraPos)
- 作用:将“相对于相机的坐标”转换为世界坐标。
- 注意:
cameraPos的 Z 值必须为正(表示在相机前方),否则转换结果无效(会在相机后方)。
示例:在相机前方 5 米处生成一个物体:
Camera mainCam = Camera.main;
// 相机空间中:原点(相机位置),Z=5(前方 5 米)
Vector3 cameraPos = new Vector3(0, 0, 5);
// 转换为世界坐标
Vector3 worldPos = mainCam.CameraToWorldPoint(cameraPos);
// 在该位置生成物体
Instantiate(Resources.Load("Cube"), worldPos, Quaternion.identity);
3. 世界空间 ↔ 屏幕空间(3D 物体 ↔ 鼠标/UI 交互)
最常用的交互转换,比如:3D 物体坐标转屏幕像素位置(用于在物体上方显示血条)、鼠标像素位置转世界坐标(用于点击地面生成物体)。
(1)世界坐标 → 屏幕坐标
API:Camera.WorldToScreenPoint(Vector3 worldPos)
- 作用:将 3D 世界坐标转换为 2D 屏幕像素坐标(左下角为 (0,0),右上角为 (Screen.width, Screen.height))。
- 特性:Z 值为“物体到相机的距离”;若物体在相机视锥外,返回的屏幕坐标可能超出屏幕范围(如 x < 0 或 x > Screen.width)。
示例:在 3D 物体上方显示 UI 血条(核心是让 UI 跟随 3D 物体的屏幕位置):
public RectTransform bloodBar; // UI 血条的 RectTransform
public Transform target; // 3D 目标物体
private Camera mainCam;
void Start()
{
mainCam = Camera.main;
}
void Update()
{
// 3D 物体世界坐标转屏幕坐标
Vector3 screenPos = mainCam.WorldToScreenPoint(target.position + new Vector3(0, 2, 0)); // 物体上方 2 米
// 将屏幕坐标转换为 UI 空间坐标(Canvas 需设为 Screen Space - Overlay)
RectTransformUtility.ScreenPointToLocalPointInRectangle(
bloodBar.parent as RectTransform, // UI 父节点(通常是 Canvas)
screenPos,
null, // 无相机(Overlay 模式)
out Vector2 localPos
);
// 设置血条位置
bloodBar.localPosition = new Vector3(localPos.x, localPos.y, 0);
}
(2)屏幕坐标 → 世界坐标
API:Camera.ScreenToWorldPoint(Vector3 screenPos)
- 关键注意:
screenPos.z必须指定“物体到相机的距离”(否则默认 Z=0,可能转换到相机近裁切面)。 - 适用场景:鼠标点击地面生成物体、射线检测起点设置等。
示例:鼠标点击地面(Y=0)生成物体:
Camera mainCam = Camera.main;
void Update()
{
if (Input.GetMouseButtonDown(0)) // 点击鼠标左键
{
// 屏幕坐标:鼠标位置(x,y)+ 距离相机 10 米(z=10)
Vector3 screenPos = new Vector3(Input.mousePosition.x, Input.mousePosition.y, 10);
// 转换为世界坐标(此时是相机前方 10 米处的点)
Vector3 worldPos = mainCam.ScreenToWorldPoint(screenPos);
// 修正 Y 轴为地面高度(Y=0)
worldPos.y = 0;
// 生成物体
Instantiate(Resources.Load("Cube"), worldPos, Quaternion.identity);
}
}
更精准的“点击地面”方案:用
Camera.ScreenPointToRay(Input.mousePosition)发射射线,检测地面碰撞点(避免 Z 值手动设置的误差)。
4. 屏幕空间 ↔ UI 空间(Rect Transform 专用)
UI 组件(如 Image、Text)的坐标是「Rect Transform 局部空间」,与屏幕空间的转换需用 RectTransformUtility 工具类(因 Canvas 模式不同,原点和范围可能不同)。
核心 API:
- 屏幕坐标 → UI 局部坐标:
RectTransformUtility.ScreenPointToLocalPointInRectangle(RectTransform parent, Vector2 screenPos, Camera cam, out Vector2 localPos) - UI 局部坐标 → 屏幕坐标:
RectTransformUtility.LocalPointToScreenPointInRectangle(RectTransform parent, Vector2 localPos, Camera cam, out Vector2 screenPos)
关键参数说明:
parent:UI 元素的父节点(通常是 Canvas 的 RectTransform,确保坐标参考系统一)。cam:Canvas 对应的相机(Screen Space - Overlay 模式时设为null;Screen Space - Camera 模式时设为指定相机)。
示例:鼠标位置转为 UI 按钮的局部坐标(判断鼠标是否在按钮内):
public RectTransform buttonRect; // 按钮的 RectTransform
private Camera uiCam; // Canvas 对应的相机(若为 Overlay 模式则设为 null)
void Start()
{
// 获取 Canvas 的相机(Screen Space - Camera 模式)
Canvas canvas = buttonRect.GetComponentInParent<Canvas>();
uiCam = canvas.worldCamera;
}
void Update()
{
// 屏幕坐标(鼠标位置)转 UI 局部坐标
if (RectTransformUtility.ScreenPointToLocalPointInRectangle(
buttonRect, // 目标 UI 的 RectTransform
Input.mousePosition,
uiCam,
out Vector2 localPos
))
{
// 判断局部坐标是否在按钮的 Rect 范围内
if (buttonRect.rect.Contains(localPos))
{
Debug.Log("鼠标在按钮内");
}
else
{
Debug.Log("鼠标在按钮外");
}
}
}
三、特殊场景:向量的坐标转换(非位置点)
上述转换均针对「位置点」(有原点参考),若需转换「向量」(无原点,仅表示方向/大小,如速度、法线),需用专门的 API(忽略位置,仅考虑旋转和缩放)。
| 转换场景 | 点(位置)API | 向量(方向)API | 区别 |
|---|---|---|---|
| 局部 → 世界 | Transform.TransformPoint |
Transform.TransformDirection / Transform.TransformVector |
向量 API 忽略物体位置,仅应用旋转和缩放 |
| 世界 → 局部 | Transform.InverseTransformPoint |
Transform.InverseTransformDirection / Transform.InverseTransformVector |
同上 |
向量 API 区别:
TransformDirection:忽略物体的缩放(仅应用旋转),适合转换“方向向量”(如前进方向、法线)。TransformVector:应用物体的缩放和旋转,适合转换“有大小的向量”(如速度、力)。
示例:子物体的局部前进方向(Z 轴)转为世界方向:
Transform child = GameObject.Find("Child").transform;
// 局部方向:Z 轴(前进方向)
Vector3 localDir = Vector3.forward;
// 转换为世界方向(忽略缩放,仅旋转)
Vector3 worldDir = child.TransformDirection(localDir);
Debug.Log("子物体世界前进方向:" + worldDir);
四、常见坑与注意事项
- Z 值不能为空:
ScreenToWorldPoint和CameraToWorldPoint必须指定 Z 值(物体到相机的距离),否则转换结果无效(Z=0 可能在相机内部)。 - Canvas 模式影响 UI 转换:
- Overlay 模式:UI 直接覆盖屏幕,
RectTransformUtility的cam参数设为null。 - Camera 模式:UI 通过相机渲染,需传入对应的相机,否则坐标偏移。
- Overlay 模式:UI 直接覆盖屏幕,
- Pivot 中心点影响局部坐标:物体的局部坐标参考系是自身的
pivot(而非模型中心),若模型中心与 pivot 不一致,需注意转换点的位置。 - 缩放继承问题:子物体的局部坐标会继承父物体的缩放,用
TransformPoint转换时会自动应用缩放,若需忽略缩放,可先将父物体缩放设为 (1,1,1),或用TransformDirection(向量)。 - 相机视锥范围:
WorldToScreenPoint转换视锥外的物体时,屏幕坐标可能超出屏幕范围(x/y 为负数或大于屏幕宽高),需判断有效性。
五、总结:核心转换流程图
局部空间 ←→ 世界空间(Transform.TransformPoint/InverseTransformPoint)
世界空间 ←→ 相机空间(Camera.WorldToCameraPoint/CameraToWorldPoint)
世界空间 ←→ 屏幕空间(Camera.WorldToScreenPoint/ScreenToWorldPoint)
屏幕空间 ←→ UI 空间(RectTransformUtility)
向量转换(Transform.TransformDirection/InverseTransformDirection)
记住:所有转换的核心是“明确当前坐标所在的空间”,根据场景选择对应的 API,无需关注底层矩阵运算,Unity 已封装好高效的实现。实际开发中,优先使用 Transform.position/localPosition 直接获取坐标,仅当需要“自定义点/向量”转换时,再调用专门的转换 API。