一、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 的坐标系统需要注意以下要点:
- 明确坐标系类型:始终清楚当前使用的坐标系
- 选择正确的转换方法:根据需求选择 TransformPoint, InverseTransformPoint 等
- 层级结构处理:通过世界坐标中转处理不同父物体下的坐标
- 性能优化:避免在 Update 中频繁转换,使用缓存
- 常见模式:掌握 屏幕-世界 坐标转换等常用模式