外观
Lesson 039|Local、World、View、Screen 坐标如何转换?
Tags: #Creator2.x #Stage04 #Coordinate #Camera #TransformDifficulty: ⭐⭐⭐☆☆
真实问题:怪物头顶血条在主相机移动后漂走了
战斗世界由 MainCamera 渲染,血条在 Canvas UI 中。开发者把怪物 node.x/y 直接赋给血条,镜头静止时勉强对得上;相机移动、缩放或适配全面屏后,血条开始漂移。
错误是跳过了两个空间:
text
怪物 Local
→ World
→ Camera/View
→ Screen
→ UI Camera/Canvas World
→ 血条父节点 LocalL028 关注输入命中,本课关注 Renderer 与 Camera 中同一位置如何跨 Local、World、View、Screen。
🎯 本课目标
本课建立渲染和 UI 排错最常用的坐标模型:
text
Local → World → View / Clip → Screen重点不是背矩阵公式,而是知道每种坐标回答什么问题。
二、四种坐标
Local
相对当前父节点的坐标:
text
Button.x = 30表示 Button 相对父节点的位置。
World
结合父节点层级后的世界位置:
text
Button World Matrix
= Panel World Matrix × Button Local Matrix只有父节点没有旋转、缩放、斜切等变换时,世界位置才可能近似理解为父子坐标直接相加。一般情况必须使用矩阵或引擎坐标转换 API。
View / Clip
经过 Camera 变换,进入相机视图或裁剪空间。它是渲染管线内部的中间坐标。
Screen
最终映射到屏幕或 Canvas 像素区域的坐标,用于 UI、触摸和屏幕边界判断。
三、坐标转换链
text
Local Position
↓ Local / World Matrix
World Position
↓ View Matrix
Camera View
↓ Projection Matrix
Clip / NDC
↓ Viewport Mapping
Screen Position2D 项目经常隐藏了其中一些步骤,但并不代表它们不存在。
四、为什么点击位置经常不对
常见错误是拿屏幕触摸坐标直接和节点本地坐标比较:
ts
if (touch.getLocationX() > node.x) {
// 可能错误
}节点可能还受到:
- 父节点偏移。
- Canvas 缩放。
- Camera 变换。
- Anchor。
- 屏幕适配。
- UI 容器坐标系。
正确思路是先统一坐标系,再做比较。
五、Anchor 对坐标的影响
节点的 position 通常描述锚点位置,而不是左上角位置:
text
anchor = (0.5, 0.5)
→ position 接近中心
anchor = (0, 1)
→ position 接近左上角同一个 position,在不同 anchor 下视觉边界不同。做 UI 命中和布局时要区分:
text
节点位置
节点内容矩形
锚点
世界边界
屏幕边界六、Camera 的作用
Camera 决定:
- 哪些世界内容进入视野。
- 世界坐标如何映射到屏幕。
- 多个摄像机的渲染顺序。
- 屏幕和世界的比例关系。
因此“节点世界坐标正确”不代表它一定在屏幕可见区域。
七、排查坐标问题的顺序
text
1. 当前值属于哪个坐标系?
2. Node 的父节点是谁?
3. 父级是否有旋转、缩放或偏移?
4. Anchor 和 Content Size 是什么?
5. 使用哪个 Camera?
6. Canvas 和屏幕适配如何配置?
7. 触摸坐标是否经过同一转换?不要一开始就修改数字试错。
八、案例:世界坐标转 UI 坐标
text
游戏角色在 World 坐标 (100, 200)
↓
Camera 将其投影到 Screen
↓
UI 标记节点需要把 Screen 位置转入 Canvas 局部坐标如果直接把 (100, 200) 赋给 UI 节点,只有在两个坐标系恰好一致时才会看起来正确。
Creator 2.4.x 中常见做法是先用 Camera 完成 World 与 Screen 转换,再用目标 UI 父节点完成 World 与 Local 转换:
ts
const screen = cc.v2();
camera.getWorldToScreenPoint(worldPosition, screen);
const uiWorld = cc.v2();
uiCamera.getScreenToWorldPoint(screen, uiWorld);
const local = uiParent.convertToNodeSpaceAR(uiWorld);
uiMarker.setPosition(local);如果 World 内容和 UI 共用同一 Camera/Canvas,实际步骤可以简化;但必须先明确每个 API 的输入、输出坐标系以及屏幕适配方式。
官方版本依据:Creator 2.4 Camera 坐标转换 API。
九、常见误区
误区一:node.x 就是屏幕 x
通常是相对父节点的本地坐标。
误区二:世界坐标就是像素坐标
世界单位到屏幕像素还要经过 Camera、Projection 和 Viewport 映射。
误区三:anchor 只影响图片对齐,不影响命中
它会影响内容相对位置和边界判断。
误区四:屏幕分辨率变化只改变画布大小
适配策略可能改变缩放和坐标映射。
十、深入推导:世界对象跟随 UI 的完整链路
为什么不能把怪物的 World Position 直接赋给血条?因为怪物和血条通常属于两棵不同的坐标树:
text
WorldRoot
└── Monster
Canvas
└── HpLayer
└── HpBarMonster.position 只在 WorldRoot 的局部空间有意义;HpBar.position 必须在 HpLayer 的局部空间表达。两者没有共同父节点,需要借助 Camera 和 Screen 空间建立桥梁。
完整推导是:
text
Monster Local
→ 乘祖先 World Matrix
→ Monster World
→ World Camera 的 View / Projection
→ Screen
→ UI Camera 的逆 View / Projection
→ UI World
→ HpLayer World Matrix 的逆矩阵
→ HpLayer LocalconvertToWorldSpaceAR 和 convertToNodeSpaceAR 中的 AR 表示以锚点参考点转换。混用 AR 与非 AR 接口,常会产生一个随节点尺寸和锚点变化的固定偏移。
排查时不要只打印 x=300, y=200,还要给值附上“空间合同”:
text
value = (300, 200)
space = Screen
camera = WorldCamera
origin = 当前 API 约定的屏幕原点
resolution = 设计分辨率或物理分辨率
owner = Monster两个数值相同不代表处于同一空间;两个空间数值不同,也可能描述屏幕上的同一点。
概念实现:
ts
const world = monster.convertToWorldSpaceAR(cc.v2());
const screen = worldCamera.getWorldToScreenPoint(world);
const uiWorld = uiCamera.getScreenToWorldPoint(screen);
const local = hpLayer.convertToNodeSpaceAR(uiWorld);
hpBar.setPosition(local);项目若使用 Creator 2D 默认 Canvas/Camera,部分空间可能可简化;但必须先证明 Camera 与 Canvas 配置。不要把简化后的巧合当通用公式。
还要决定血条跟随点:
text
怪物锚点
+ 本地头顶偏移
→ 世界跟随点若直接使用怪物锚点,角色资源高度与锚点不同会造成视觉偏差。
十一、Creator 2.4.x 实验:同一点穿越四个空间
场景结构
text
WorldRoot
└── Monster
Canvas
└── HpLayer
└── HpBar
MainCamera
UICamera(若项目采用独立 UI Camera)Camera 配置通过 Inspector 完成。
CoordinateChainProbe.ts
ts
const { ccclass, property } = cc._decorator;
@ccclass
export default class CoordinateChainProbe extends cc.Component {
@property(cc.Node)
monster: cc.Node = null;
@property(cc.Node)
hpBar: cc.Node = null;
@property(cc.Camera)
worldCamera: cc.Camera = null;
@property(cc.Camera)
uiCamera: cc.Camera = null;
@property(cc.Node)
hpLayer: cc.Node = null;
private worldPoint = cc.v2();
private screenPoint = cc.v2();
private uiWorldPoint = cc.v2();
lateUpdate() {
this.worldPoint = this.monster.convertToWorldSpaceAR(
cc.v2(0, this.monster.height * (1 - this.monster.anchorY))
);
this.worldCamera.getWorldToScreenPoint(
this.worldPoint,
this.screenPoint
);
this.uiCamera.getScreenToWorldPoint(
this.screenPoint,
this.uiWorldPoint
);
const local = this.hpLayer.convertToNodeSpaceAR(
this.uiWorldPoint
);
this.hpBar.setPosition(local);
}
}Creator 2.4.x 不同小版本的向量参数/返回类型声明可能有差异,以项目 API 和类型提示调整;核心是显式使用负责两端渲染的 Camera。
实验步骤
- 移动 Monster,确认 HpBar 跟随。
- 移动与缩放 MainCamera,确认链路仍正确。
- 改变 Canvas 适配选项,在宽屏和窄屏预览。
- 给 HpLayer 加 position/scale,确认最后一步父空间转换仍正确。
- 故意跳过 screen → UI world,观察偏移并记录条件。
十二、View 空间为什么平时看不见
从 World 到 Screen 不是简单减去 Camera Position,而是一条变换链:
text
World Position
→ View Matrix:改写成以 Camera 为参考
→ Projection Matrix:正交或透视投影
→ Clip / NDC:归一化可见范围
→ Viewport:映射到屏幕矩形正交 Camera 中,物体通常不随深度产生透视缩小;透视 Camera 中,深度会影响屏幕位置和尺寸。把 World x/y 直接赋给 UI,相当于跳过 Camera 的全部观察规则。
项目里还可能同时存在物理像素、设计分辨率、Canvas 适配区域、Camera Viewport、安全区域和 Web CSS 像素。如果错误只发生在长屏或高 DPI 设备,应优先检查某一步是否混用了屏幕尺度或原点,而不是直接怀疑矩阵算法。
View 空间是从 Camera 观察坐标系表达世界点的中间结果。开发者常通过 Camera API 直接完成 World ↔ Screen,因此没有手写 View Matrix。
渲染数学链:
text
Local Position
× Model Matrix
→ World
× View Matrix
→ View
× Projection Matrix
→ Clip/NDC
→ Viewport 映射
→ Screen理解中间层有助于解释:
- Camera 移动为何改变 Screen 但不改变 World;
- 透视投影中远处对象为何更小;
- 多 Camera 为何给同一 World 点不同 Screen;
- Viewport 不满屏时为何需要额外映射。
十三、真实项目故障:小地图标记在旋转后反向移动
错误实现
ts
marker.x = target.x - player.x;
marker.y = target.y - player.y;它只做平移差,没有把世界方向转换进旋转后的小地图局部空间,也没有处理比例和边界。
正确推理
text
目标世界点
→ 以玩家/地图根为参考转换到地图局部
→ 按地图比例缩放
→ 根据半径/矩形边界裁剪
→ 赋给 marker若小地图 North-up,不应继承玩家旋转;若 Heading-up,则需要明确应用相机/玩家朝向。先确定产品坐标语义,再写矩阵链。
十四、坐标故障诊断
固定偏移
优先检查锚点、父节点原点、Canvas 对齐和固定安全区补偿。
随相机移动而偏移
检查是否跳过 World → Screen 或使用错误 Camera。
随父节点缩放而放大误差
检查是否把 screen/world 结果直接赋给局部 position。
只在部分分辨率错
检查 View/Canvas 适配、Viewport 和物理像素/设计坐标混用。
每一步打印语义化日志:
text
monsterWorld
worldCameraScreen
uiCameraWorld
hpLayerLocal不要只打印四组没有名称的 x/y。
十五、练习、推导答案与版本边界
本课以 Creator 2.4.x Node 与 Camera 坐标 API 为主。Creator 3.x 使用 Vec3、UITransform 和不同 Camera 接口,不能原样复制。
- Camera 移动后,怪物 World 是否改变? 怪物自身 World 通常不因 Camera 移动改变,但它在 View/Screen 中的位置改变。
- 为什么 Screen 点不能直接赋给 Canvas 深层子节点 position? position 属于父节点 Local;还要经过 UI Camera/Canvas 世界映射和父节点逆变换。
- 锚点怎样影响跟随点?
convertToWorldSpaceAR(0,0)取得的是锚点世界位置;头顶位置要基于尺寸与 anchor 加局部偏移。 - 两个 Camera 看同一对象为何 Screen 结果可能不同? 它们的 View、Projection 和 Viewport 可以不同。
能力验收
你应该能把一个世界坐标依次转换为 Screen 和目标 UI 父节点 Local 坐标,记录每一级中间值,并根据固定偏移、随相机偏移、随父级缩放、仅部分分辨率错误反推问题所在空间。
本课总结
text
先确认坐标系,再谈数值。
Local 不等于 World,World 不等于 Screen。
Anchor、父节点、Camera 和适配都会影响最终位置。十二、下一课预告
text
Lesson040|Camera、可见性判断与渲染顺序