一、Unity 中的坐标系类型

1. 模型坐标系 (Model Space)

  • 定义:物体自身的坐标系
  • 原点:物体的中心点(或网格原点)
  • 特点:与物体绑定,随物体移动旋转
  • 访问:transform.localPosition, transform.localRotation, transform.localScale

2. 世界坐标系 (World Space)

  • 定义:场景全局坐标系
  • 原点:场景原点 (0,0,0)
  • 特点:所有物体共享的统一坐标系
  • 访问:transform.position, transform.rotation, transform.lossyScale

3. 局部坐标系 (Local Space)

  • 定义:相对于父物体的坐标系
  • 原点:父物体的位置
  • 特点:用于层级结构中的相对位置
  • 访问:transform.localPosition, transform.localRotation

4. 屏幕坐标系 (Screen Space)

  • 定义:基于屏幕的2D坐标系
  • 原点:屏幕左下角 (0,0)
  • 单位:像素
  • 范围:
    • X: 0 到 Screen.width
    • Y: 0 到 Screen.height
    • Z: 相机到物体的距离

5. 视口坐标系 (Viewport Space)

  • 定义:归一化的屏幕坐标系
  • 原点:屏幕左下角 (0,0)
  • 范围:X/Y 从 0 到 1
  • 特点:与屏幕分辨率无关

二、常用坐标转换方法

1. 世界坐标 <=> 屏幕坐标

// 世界坐标转屏幕坐标
Vector3 screenPos = Camera.main.WorldToScreenPoint(worldPosition);

// 屏幕坐标转世界坐标
Vector3 worldPos = Camera.main.ScreenToWorldPoint(screenPosition);

使用场景:

  • 在3D物体上方显示UI元素(如血条)
  • 将鼠标点击位置转换为3D世界中的位置
  • 实现物体跟随鼠标移动

2. 世界坐标 <=> 视口坐标

// 世界坐标转视口坐标
Vector3 viewportPos = Camera.main.WorldToViewportPoint(worldPosition);

// 视口坐标转世界坐标
Vector3 worldPos = Camera.main.ViewportToWorldPoint(viewportPosition);

使用场景:

  • 判断物体是否在相机视野内
  • 实现小地图功能
  • 物体在屏幕边缘提示

3. 局部坐标 <=> 世界坐标

// 局部坐标转世界坐标
Vector3 worldPos = transform.TransformPoint(localPosition);

// 世界坐标转局部坐标
Vector3 localPos = transform.InverseTransformPoint(worldPosition);

使用场景:

  • 在层级结构中计算子物体的世界位置
  • 将全局位置转换为相对于某个物体的位置
  • 在父物体坐标系中生成子物体

4. 方向转换

// 局部方向转世界方向
Vector3 worldDir = transform.TransformDirection(localDirection);

// 世界方向转局部方向
Vector3 localDir = transform.InverseTransformDirection(worldDirection);

使用场景:

  • 控制角色移动方向(将输入方向转换为世界方向)
  • 物理计算中转换力的方向
  • 射线检测时转换方向

5. 向量转换(忽略位置)

// 局部向量转世界向量
Vector3 worldVec = transform.TransformVector(localVector);

// 世界向量转局部向量
Vector3 localVec = transform.InverseTransformVector(worldVector);

使用场景:

  • 计算相对速度
  • 处理非方向性的向量(如偏移量)
  • 在局部空间中处理物理效果

三、坐标转换最佳实践

1. 明确坐标系类型
关键原则:始终明确当前使用的坐标系类型

// 不好的做法:混淆坐标系
transform.position = new Vector3(10, 0, 0); // 世界坐标
transform.localPosition = new Vector3(5, 0, 0); // 局部坐标

// 好的做法:明确注释
Vector3 worldPosition = new Vector3(10, 0, 0); // 世界坐标
transform.position = worldPosition;

Vector3 localPosition = new Vector3(5, 0, 0); // 相对于父物体的位置
transform.localPosition = localPosition;

2. 层级结构中的坐标处理
问题:不同父节点下的坐标转换
解决方案:

// 将物体A的局部坐标转换为物体B的局部坐标
Vector3 worldPos = objA.transform.TransformPoint(localPosInA);
Vector3 localPosInB = objB.transform.InverseTransformPoint(worldPos);

使用场景:

  • 在复杂层级结构中定位物体
  • 计算两个不同父物体下子物体的相对位置

3. 性能优化
避免在Update中频繁转换:

private Vector3 _cachedScreenPos;

void Start()
{
    // 预先计算
    _cachedScreenPos = Camera.main.WorldToScreenPoint(transform.position);
}

void Update()
{
    // 使用缓存值
    uiElement.transform.position = _cachedScreenPos;
}

4. 常用转换模式

模式1:屏幕坐标转世界坐标(3D位置)

Vector3 GetWorldPosFromScreen(Vector2 screenPos, float distanceFromCamera)
{
    Ray ray = Camera.main.ScreenPointToRay(screenPos);
    return ray.GetPoint(distanceFromCamera);
}

模式2:世界坐标转UI位置

RectTransform GetUIPosition(Transform worldObject)
{
    Vector3 screenPos = Camera.main.WorldToScreenPoint(worldObject.position);
    RectTransformUtility.ScreenPointToLocalPointInRectangle(
        canvasRect, 
        screenPos, 
        null, 
        out Vector2 localPoint
    );
    return localPoint;
}

模式3:物体间相对位置计算

Vector3 GetRelativePosition(Transform from, Transform to)
{
    Vector3 worldPos = to.position;
    return from.InverseTransformPoint(worldPos);
}

四、设置寻路点位的坐标处理

1. 设置目标点位的推荐方式

// 使用世界坐标设置目标点
public void SetTargetPosition(Vector3 worldPosition)
{
    // 存储为世界坐标
    _targetWorldPosition = worldPosition;
    
    // 转换为局部坐标(如果需要)
    _targetLocalPosition = transform.parent.InverseTransformPoint(worldPosition);
}

// 在Update中使用
void Update()
{
    // 直接使用世界坐标
    Vector3 direction = (_targetWorldPosition - transform.position).normalized;
    transform.position += direction * speed * Time.deltaTime;
}

2. 不同父节点下的解决方案

// 获取相对于当前父物体的目标位置
Vector3 GetTargetPositionRelative(Transform target)
{
    // 方案1:通过世界坐标中转
    Vector3 worldPos = target.position;
    return transform.parent.InverseTransformPoint(worldPos);
    
    // 方案2:直接计算相对位置
    Vector3 relativePos = target.position - transform.parent.position;
    return Quaternion.Inverse(transform.parent.rotation) * relativePos;
}

3. 坐标系选择建议

场景 推荐坐标系 原因
独立物体移动 世界坐标 计算简单,无需考虑父物体
层级结构中的物体 局部坐标 保持与父物体的相对关系
物理模拟 世界坐标 物理引擎使用世界坐标
UI元素定位 屏幕坐标 UI系统基于屏幕坐标
相机相关计算 视口坐标 与屏幕分辨率无关

五、常见问题解决方案

问题1:物体位置设置不正确

可能原因:混淆了 localPosition 和 position
解决方案:

// 明确使用世界坐标
transform.position = worldPosition;

// 明确使用局部坐标
transform.localPosition = localPosition;

问题2:旋转后位置计算错误

解决方案:

// 使用方向向量而非位置差
Vector3 direction = (target.position - transform.position).normalized;
transform.rotation = Quaternion.LookRotation(direction);

问题3:屏幕坐标转换不准确

解决方案:

// 确保考虑Z轴深度
Vector3 screenPos = new Vector3(Input.mousePosition.x, Input.mousePosition.y, distanceFromCamera);
Vector3 worldPos = Camera.main.ScreenToWorldPoint(screenPos);

问题4:不同缩放层级下的位置错误

解决方案:

// 使用TransformPoint处理缩放
Vector3 worldPos = transform.TransformPoint(localPosition);

六、矩阵变换

// 获取世界转局部矩阵
Matrix4x4 localToWorldMatrix = transform.localToWorldMatrix;

// 获取局部转世界矩阵
Matrix4x4 worldToLocalMatrix = transform.worldToLocalMatrix;

// 使用矩阵转换位置
Vector3 worldPos = localToWorldMatrix.MultiplyPoint3x4(localPosition);
Vector3 localPos = worldToLocalMatrix.MultiplyPoint3x4(worldPosition);

// 使用矩阵转换方向
Vector3 worldDir = localToWorldMatrix.MultiplyVector(localDirection);
Vector3 localDir = worldToLocalMatrix.MultiplyVector(worldDirection);

使用场景:

  • 批量处理多个物体的坐标转换
  • 自定义渲染时处理顶点位置
  • 复杂坐标变换链

总结

Unity 的坐标系统需要注意以下要点:

  1. 明确坐标系类型:始终清楚当前使用的坐标系
  2. 选择正确的转换方法:根据需求选择 TransformPoint, InverseTransformPoint 等
  3. 层级结构处理:通过世界坐标中转处理不同父物体下的坐标
  4. 性能优化:避免在 Update 中频繁转换,使用缓存
  5. 常见模式:掌握 屏幕-世界 坐标转换等常用模式