在 Unity 中,不同坐标空间的转换核心是 明确“源空间”和“目标空间”,再通过 Unity 封装的 API 实现映射(底层是矩阵运算,无需手动处理)。以下是 按“转换场景”分类的完整方案,含 API 用法、示例和关键注意点,覆盖所有常用坐标空间交互:

一、先理清:核心坐标空间关系

所有转换都围绕 4 大核心空间展开,关系如下:

局部空间 ↔ 世界空间 ↔ 相机空间 ↔ 屏幕空间
                          ↓
                      UI 空间(RectTransform)
  • 「局部↔世界」:父子物体层级相关,用 Transform 类 API;
  • 「世界↔相机」:3D 物体相对相机位置,用 Camera 类 API;
  • 「世界↔屏幕」:3D 与鼠标/UI 交互,用 Camera 类 API;
  • 「屏幕↔UI」:UI 定位/鼠标判断,用 RectTransformUtility 工具类;
  • 向量转换(无原点):用 Transform 专属向量 API(忽略位置,仅处理旋转/缩放)。

二、分场景实现:坐标空间转换(含代码+用法)

场景 1:局部空间 ↔ 世界空间(父子物体/自定义点)

核心是将“相对于某个物体 pivot 的局部坐标”与“全局唯一的世界坐标”互转,需用 Transform 类 API。

转换方向 核心 API 作用 注意事项
局部 → 世界 Transform.TransformPoint(Vector3 localPos) 把“相对于当前物体的局部点”转世界坐标 会应用当前物体的位置、旋转、缩放
世界 → 局部 Transform.InverseTransformPoint(Vector3 worldPos) 把“世界点”转“相对于当前物体的局部点” 同上,需注意物体 pivot 位置

示例 1:子物体自定义局部点转世界坐标

父物体 Parent 位置 (2,0,0)、旋转 90°,子物体相对于父物体的“自定义局部点” (1,0,0),转世界坐标:

Transform parent = GameObject.Find("Parent").transform;
Vector3 localCustomPos = new Vector3(1, 0, 0); // 父物体局部空间的点
Vector3 worldPos = parent.TransformPoint(localCustomPos); 
// 结果:父物体位置 + 旋转后的局部点(因父旋转 90°,最终世界坐标可能是 (2,0,1))
Debug.Log("自定义局部点的世界坐标:" + worldPos);

示例 2:世界坐标转父物体局部坐标

已知世界坐标 (5,0,3),转成相对于 Parent 的局部坐标:

Transform parent = GameObject.Find("Parent").transform;
Vector3 worldPos = new Vector3(5, 0, 3);
Vector3 localPos = parent.InverseTransformPoint(worldPos);
Debug.Log("相对于父物体的局部坐标:" + localPos);

简化用法(无需 API):

  • 直接获取物体自身的局部坐标:transform.localPosition(相对于父物体);
  • 直接获取物体的世界坐标:transform.position
  • 仅当需要转换“非自身 pivot 的自定义点”时,才用 TransformPoint/InverseTransformPoint

场景 2:世界空间 ↔ 相机空间(3D 物体相对相机)

相机空间以相机为原点,Z 轴指向世界 -Z 方向(Unity 相机默认“看向自身 -Z 轴”),用于判断物体相对相机的位置(如前方/后方)。

转换方向 核心 API 作用 关键特性
世界 → 相机 Camera.WorldToCameraPoint(Vector3 worldPos) 世界点转“相对于相机的坐标” Z 值 = 物体到相机的距离(正值=前方)
相机 → 世界 Camera.CameraToWorldPoint(Vector3 cameraPos) 相机局部点转世界坐标 必须指定 Z 值(正值=相机前方,否则无效)

示例 1:判断物体是否在相机前方

Camera mainCam = Camera.main;
Transform target = GameObject.Find("Target").transform;

Vector3 cameraSpacePos = mainCam.WorldToCameraPoint(target.position);
if (cameraSpacePos.z > 0)
{
    Debug.Log("物体在相机前方(可见)");
}
else
{
    Debug.Log("物体在相机后方(不可见)");
}

示例 2:在相机前方 10 米处生成物体

Camera mainCam = Camera.main;
// 相机空间坐标:原点(相机位置)+ Z=10(前方 10 米)
Vector3 cameraLocalPos = new Vector3(0, 1, 10); // Y 轴偏移 1 米(避免贴地)
Vector3 worldPos = mainCam.CameraToWorldPoint(cameraLocalPos);
Instantiate(Resources.Load("Cube"), worldPos, Quaternion.identity);

场景 3:世界空间 ↔ 屏幕空间(3D ↔ 鼠标/UI 交互)

屏幕空间是像素坐标系(左下角 (0,0),右上角 (Screen.width, Screen.height)),核心用于“3D 物体转屏幕位置”(如血条跟随)或“鼠标位置转世界位置”(如点击生成)。

转换方向 核心 API 作用 关键注意
世界 → 屏幕 Camera.WorldToScreenPoint(Vector3 worldPos) 3D 世界点转 2D 屏幕像素坐标 Z 值 = 物体到相机的距离;视锥外的点会超出屏幕范围
屏幕 → 世界 Camera.ScreenToWorldPoint(Vector3 screenPos) 屏幕像素点转 3D 世界坐标 必须指定 screenPos.z(物体到相机的距离),否则 Z=0 无效

示例 1:3D 物体上方显示 UI 血条(世界→屏幕)

public RectTransform bloodBar; // UI 血条(Canvas 设为 Screen Space - Overlay)
public Transform target; // 3D 目标物体
private Camera mainCam;

void Start() => mainCam = Camera.main;

void Update()
{
    // 1. 世界坐标转屏幕坐标(物体上方 2 米,避免遮挡)
    Vector3 screenPos = mainCam.WorldToScreenPoint(target.position + new Vector3(0, 2, 0));
    
    // 2. 屏幕坐标转 UI 局部坐标(适配 Canvas 空间)
    RectTransformUtility.ScreenPointToLocalPointInRectangle(
        bloodBar.parent as RectTransform, // 父节点(Canvas)
        screenPos,
        null, // Overlay 模式设为 null
        out Vector2 uiLocalPos
    );
    
    // 3. 设置血条位置
    bloodBar.localPosition = new Vector3(uiLocalPos.x, uiLocalPos.y, 0);
}

示例 2:鼠标点击地面生成物体(屏幕→世界)

Camera mainCam = Camera.main;

void Update()
{
    if (Input.GetMouseButtonDown(0))
    {
        // 1. 构建屏幕坐标:鼠标位置 + Z=10(相机前方 10 米,确保在视锥内)
        Vector3 screenPos = new Vector3(Input.mousePosition.x, Input.mousePosition.y, 10);
        
        // 2. 屏幕坐标转世界坐标
        Vector3 worldPos = mainCam.ScreenToWorldPoint(screenPos);
        
        // 3. 修正 Y 轴为地面高度(假设地面 Y=0)
        worldPos.y = 0;
        
        // 4. 生成物体
        Instantiate(Resources.Load("Cube"), worldPos, Quaternion.identity);
    }
}

更精准方案:用射线检测替代手动 Z 值设置(避免地面高度不确定):

if (Input.GetMouseButtonDown(0) && Physics.Raycast(mainCam.ScreenPointToRay(Input.mousePosition), out RaycastHit hit))
{
    Instantiate(Resources.Load("Cube"), hit.point, Quaternion.identity); // hit.point 是地面碰撞点(世界坐标)
}

场景 4:屏幕空间 ↔ UI 空间(RectTransform 专属)

UI 组件(Image、Button 等)的坐标是「RectTransform 局部空间」,与屏幕空间的转换需用 RectTransformUtility(因 Canvas 模式不同,参考系不同)。

转换方向 核心 API 作用 关键参数
屏幕 → UI RectTransformUtility.ScreenPointToLocalPointInRectangle(父 RectTransform, 屏幕坐标, 相机, out 局部坐标) 屏幕像素点转 UI 局部坐标 相机:Overlay 模式设为 null;Camera 模式传 Canvas 相机
UI → 屏幕 RectTransformUtility.LocalPointToScreenPointInRectangle(父 RectTransform, UI 局部坐标, 相机, out 屏幕坐标) UI 局部点转屏幕像素坐标 同上

示例:判断鼠标是否在 UI 按钮内(屏幕→UI)

public RectTransform buttonRect; // 按钮的 RectTransform
private Camera uiCam; // Canvas 对应的相机(Camera 模式用)

void Start()
{
    Canvas canvas = buttonRect.GetComponentInParent<Canvas>();
    uiCam = canvas.renderMode == RenderMode.ScreenSpaceOverlay ? null : canvas.worldCamera;
}

void Update()
{
    // 屏幕坐标(鼠标)转 UI 局部坐标
    if (RectTransformUtility.ScreenPointToLocalPointInRectangle(
        buttonRect, // 目标 UI 的 RectTransform
        Input.mousePosition,
        uiCam,
        out Vector2 uiLocalPos
    ))
    {
        // 判断局部坐标是否在按钮 Rect 范围内
        if (buttonRect.rect.Contains(uiLocalPos))
        {
            Debug.Log("鼠标在按钮内");
        }
    }
}

场景 5:向量转换(非位置点,仅方向/大小)

上述转换均针对「位置点」(有原点参考),若需转换「向量」(如速度、前进方向、法线),需用专门的 API(忽略位置,仅处理旋转和缩放)。

转换方向 向量 API(方向/大小) 点 API(位置) 区别
局部 → 世界 Transform.TransformDirection(Vector3 localDir) Transform.TransformPoint 向量 API 忽略物体位置,仅应用旋转
局部 → 世界 Transform.TransformVector(Vector3 localVec) - 应用旋转+缩放(适合有大小的向量,如速度)
世界 → 局部 Transform.InverseTransformDirection(Vector3 worldDir) Transform.InverseTransformPoint 忽略位置,仅反向旋转
世界 → 局部 Transform.InverseTransformVector(Vector3 worldVec) - 反向旋转+反向缩放

示例:子物体局部前进方向转世界方向

Transform child = GameObject.Find("Child").transform;
Vector3 localForward = Vector3.forward; // 局部 Z 轴(前进方向)

// 转换为世界方向(忽略缩放,仅旋转)
Vector3 worldForward = child.TransformDirection(localForward);
Debug.Log("子物体世界前进方向:" + worldForward);

// 若需保留缩放(如速度向量),用 TransformVector
Vector3 localSpeed = new Vector3(0, 0, 5); // 局部速度 5m/s
Vector3 worldSpeed = child.TransformVector(localSpeed);

三、关键注意事项(避坑核心)

  1. Z 值不能省略
    • ScreenToWorldPointCameraToWorldPoint 必须指定 Z 值(物体到相机的距离),否则 Z=0 会转换到相机内部,结果无效。
  2. Canvas 模式影响 UI 转换
    • Overlay 模式:UI 直接覆盖屏幕,RectTransformUtilitycamera 参数设为 null
    • Camera 模式:UI 通过相机渲染,必须传入 Canvas 的 worldCamera,否则坐标偏移。
  3. Pivot 中心点的影响
    物体的局部坐标参考系是自身 pivot(而非模型中心),若模型中心与 pivot 不一致(如 pivot 在底部),转换时需注意点的位置。
  4. 视锥范围判断
    WorldToScreenPoint 转换视锥外的物体时,屏幕坐标可能超出屏幕范围(x<0、x>Screen.width 等),需先判断有效性:
    Vector3 screenPos = mainCam.WorldToScreenPoint(target.position);
    if (screenPos.x >= 0 && screenPos.x <= Screen.width && screenPos.y >= 0 && screenPos.y <= Screen.height && screenPos.z > 0)
    {
        // 物体在屏幕内
    }
    
  5. 缩放继承问题
    子物体的局部坐标会继承父物体的缩放,TransformPoint 会自动应用缩放;若需忽略缩放,可用 TransformDirection(向量)或临时重置父物体缩放。

四、总结:转换流程速查

  1. 明确「源空间」和「目标空间」(如“世界→屏幕”“屏幕→UI”);
  2. 根据空间类型选择 API :
    • 局部↔世界:Transform.TransformPoint/InverseTransformPoint
    • 世界↔相机:Camera.WorldToCameraPoint/CameraToWorldPoint
    • 世界↔屏幕:Camera.WorldToScreenPoint/ScreenToWorldPoint
    • 屏幕↔UI:RectTransformUtility
    • 向量转换:Transform.TransformDirection/InverseTransformDirection
  3. 检查关键参数(如 Z 值、相机、Canvas 模式),避免无效转换。

按这个逻辑,所有坐标空间转换场景都能覆盖,且无需关注底层矩阵运算,直接用 Unity 封装的 API 即可高效实现。