Skip to content

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
→ 血条父节点 Local

L028 关注输入命中,本课关注 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 Position

2D 项目经常隐藏了其中一些步骤,但并不代表它们不存在。


四、为什么点击位置经常不对 ​

常见错误是拿屏幕触摸坐标直接和节点本地坐标比较:

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
    └── HpBar

Monster.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 Local

convertToWorldSpaceAR 和 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。

实验步骤 ​

  1. 移动 Monster,确认 HpBar 跟随。
  2. 移动与缩放 MainCamera,确认链路仍正确。
  3. 改变 Canvas 适配选项,在宽屏和窄屏预览。
  4. 给 HpLayer 加 position/scale,确认最后一步父空间转换仍正确。
  5. 故意跳过 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 接口,不能原样复制。

  1. Camera 移动后,怪物 World 是否改变? 怪物自身 World 通常不因 Camera 移动改变,但它在 View/Screen 中的位置改变。
  2. 为什么 Screen 点不能直接赋给 Canvas 深层子节点 position? position 属于父节点 Local;还要经过 UI Camera/Canvas 世界映射和父节点逆变换。
  3. 锚点怎样影响跟随点? convertToWorldSpaceAR(0,0) 取得的是锚点世界位置;头顶位置要基于尺寸与 anchor 加局部偏移。
  4. 两个 Camera 看同一对象为何 Screen 结果可能不同? 它们的 View、Projection 和 Viewport 可以不同。

能力验收 ​

你应该能把一个世界坐标依次转换为 Screen 和目标 UI 父节点 Local 坐标,记录每一级中间值,并根据固定偏移、随相机偏移、随父级缩放、仅部分分辨率错误反推问题所在空间。

本课总结 ​

text
先确认坐标系,再谈数值。
Local 不等于 World,World 不等于 Screen。
Anchor、父节点、Camera 和适配都会影响最终位置。

十二、下一课预告 ​

text
Lesson040|Camera、可见性判断与渲染顺序