在 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 提供了 TransformCamera 类的封装 API,覆盖绝大多数转换场景,无需手动操作矩阵。以下是 最常用的 6 种转换,含代码示例和适用场景:

1. 局部空间 ↔ 世界空间(父子物体/自身位置转换)

最基础的转换,用于将子物体的局部位置转为世界位置,或反之。

(1)局部坐标 → 世界坐标

APITransform.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)世界坐标 → 局部坐标

APITransform.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)世界坐标 → 相机坐标

APICamera.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)相机坐标 → 世界坐标

APICamera.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)世界坐标 → 屏幕坐标

APICamera.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)屏幕坐标 → 世界坐标

APICamera.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);

四、常见坑与注意事项

  1. Z 值不能为空ScreenToWorldPointCameraToWorldPoint 必须指定 Z 值(物体到相机的距离),否则转换结果无效(Z=0 可能在相机内部)。
  2. Canvas 模式影响 UI 转换
    • Overlay 模式:UI 直接覆盖屏幕,RectTransformUtilitycam 参数设为 null
    • Camera 模式:UI 通过相机渲染,需传入对应的相机,否则坐标偏移。
  3. Pivot 中心点影响局部坐标:物体的局部坐标参考系是自身的 pivot(而非模型中心),若模型中心与 pivot 不一致,需注意转换点的位置。
  4. 缩放继承问题:子物体的局部坐标会继承父物体的缩放,用 TransformPoint 转换时会自动应用缩放,若需忽略缩放,可先将父物体缩放设为 (1,1,1),或用 TransformDirection(向量)。
  5. 相机视锥范围WorldToScreenPoint 转换视锥外的物体时,屏幕坐标可能超出屏幕范围(x/y 为负数或大于屏幕宽高),需判断有效性。

五、总结:核心转换流程图

局部空间 ←→ 世界空间(Transform.TransformPoint/InverseTransformPoint)
世界空间 ←→ 相机空间(Camera.WorldToCameraPoint/CameraToWorldPoint)
世界空间 ←→ 屏幕空间(Camera.WorldToScreenPoint/ScreenToWorldPoint)
屏幕空间 ←→ UI 空间(RectTransformUtility)
向量转换(Transform.TransformDirection/InverseTransformDirection)

记住:所有转换的核心是“明确当前坐标所在的空间”,根据场景选择对应的 API,无需关注底层矩阵运算,Unity 已封装好高效的实现。实际开发中,优先使用 Transform.position/localPosition 直接获取坐标,仅当需要“自定义点/向量”转换时,再调用专门的转换 API。