简介:围绕 Cocos Creator 2.4.10 构建的 2D 人物与 3D 背景混合场景示例,面向希望理解透视视角、角色移动与二维三维元素协同的初中级游戏开发者,可用于学习摄像机参数配置、节点位移控制和输入监听等基础交互思路。压缩包共 33 个文件,约 313KB,包含 4 个 ts 脚本、15 个 meta 配置、6 个 json 与 4 个 png 贴图,另有 fire 场景、atlas 图集等,分别对应逻辑控制、资源描述、场景搭建与美术素材,体积轻量便于快速导入编辑器查看结构。已有 469 人学习,说明该案例在入门实践中具有一定参考价值。读者可从中梳理 2D 精灵与 3D 场景并存的工程组织方式,观察人物移动脚本如何结合键盘或触摸输入更新位置,并借鉴透视摄像机的视角设置与项目配置流程;虽规模不大,但胜在聚焦 2.4.10 版本下的混合渲染与基础交互,适合作为小型实验原型和功能验证的起点。
1. 2D 纸片人站在 3D 地面上:透视视角真正的难点是“对齐成本”
cocoscreator 游戏开发里有一类需求看着特别省事:人物用 2D 精灵,背景用 3D 场景,相机给一个俯视的透视角度,人物在地面上走。第一次做的人多半会把它理解成“把 Sprite 拖进 3D 场景”,然后就会接连撞上几件事:人物随距离缩小、斜着看变成一条纸片、点击地面人物跑到别处、近处的石头挡不住人。
这些不是零散的 bug,根源是同一个:2D 渲染组件只认节点树顺序和屏幕平面,透视相机只认世界坐标和相机空间深度。把两者接起来,靠的是一套显式的坐标换算和相机分工,引擎不会自动替你做。搞懂这套换算,你才知道哪些参数能动、哪些坑必须提前埋。
2. 投影链路:一个 2D 精灵在透视相机里到底经历了什么
2.1 从节点世界矩阵到裁剪空间:谁决定人物的大小和位置
Cocos Creator 2.4 这条线是 2.x 的收尾版本,3D 能力是从后续版本回移植过来的,能用,但不是引擎的主角。理解它的第一步,是承认 Sprite 在这套渲染器里并不特殊:cc.Sprite通过 assembler 生成四个顶点、两个三角形,顶点坐标在节点本地空间,以锚点为原点;合批阶段会把节点的世界矩阵乘到这些顶点上,写进动态顶点缓冲。也就是说,进入相机之前,精灵顶点已经是世界坐标了。
这一步很关键。很多人以为 2D 合批之后精灵就“退化”成屏幕上的图,实际上合批只是把多个精灵塞进同一个 drawcall,每个顶点仍然带着自己的世界位置。透视相机的视图矩阵和投影矩阵会一视同仁地作用在这些顶点上,所以你把一个 Sprite 放到 z = -20 的位置,它就会像任何 3D 物体一样被投影、被缩小、被近大远小。
但绘制顺序是另一条线。2.x 的 2D 渲染组件走的是类似 UI 的树序遍历:同一个父节点下用zIndex调整,跨父节点按节点树顺序,渲染组件在入队时拿到一个顺序号。这个顺序号决定谁先画谁后画,和顶点的世界 z 没有关系。换句话说,位置由矩阵决定,遮挡由树序决定,这两件事在 2.x 里是分开的。你调一个精灵的 z,只会让它投影到屏幕上的位置变化,不会让它排到另一个精灵后面。这条认知是后面所有翻车点的源头。
所以第一个结论可以先落地:2D 精灵能被透视相机正确投影,是因为它本质上就是贴在 3D 空间里的一个四边形;它不能自动获得正确的 3D 遮挡关系,是因为它的排序逻辑还停留在 2D 那一套。
2.2 为什么 z 一变人物就缩小:fov 与深度的反比
透视投影的数学不复杂,但值得亲手推一遍,因为后面所有“补偿”都是从这条公式长出来的。设相机看向 -Z,某个顶点在相机空间的深度是 d(d 为正),相机的垂直视场角是 fov,屏幕高度是 H。这个顶点在 NDC 里的 y 坐标是:
ndc.y = (y_cam / d) / tan(fov / 2)换算到屏幕像素,还要再乘 H/2。反过来看,在深度 d 处,屏幕整屏高度对应的世界高度是:
worldHeight(d) = 2 * d * tan(fov / 2)这个式子说明两件事。第一,同一个世界尺寸的精灵,深度翻倍,屏幕上的像素高度就减半,这是纯几何,没有商量余地。第二,fov 越大,单位深度覆盖的世界范围越大,同样大小的精灵看起来越小;fov 越小,画面越“长焦”,纵深压缩得越厉害,人物在远近之间的尺寸差异越不明显。
一个具体例子:fov = 45°、d = 10 时,屏幕高度对应约 8.28 个世界单位;一个高 2 单位的精灵占屏幕高度的 24%。当人物走到 d = 20,同一精灵只占 12%。如果人物活动范围从 5 到 30,尺寸差异接近 6 倍,这就是“人物越走越小”的来源。
| 参数 | 作用 | 调大之后 |
|---|---|---|
fov | 垂直视场角 | 视野更广,纵深压缩更弱,远近尺寸差异更明显 |
nearClip | 近裁剪面 | 太大会裁掉贴近相机的人物,太小会损失深度精度 |
farClip | 远裁剪面 | 影响背景可见范围,过大会稀释深度缓冲精度 |
orthoHeight | 正交视图的世界高度 | 只在正交相机下生效,透视相机忽略它 |
这里没有“正确值”,只有取舍。俯视角的 2D 人物游戏通常把 fov 压在 30° 到 45° 之间,让纵深有感觉但不至于让角色尺寸变化过于夸张;如果相机俯角很大,fov 还要再收一点,否则地面会被拉得很“广角”。
注意:2.4 的
nearClip默认值偏大,人物贴近相机时可能被整块裁掉。相机跟随距离近的场景,先把 nearClip 调到 0.1 量级再调别的。
2.3 反投影:屏幕点如何还原成一条世界射线
理解了正向投影,反投影就是它的逆运算。屏幕上的一个点,在 3D 空间里对应的不是“一个点”,而是一条从相机出发穿过该点的射线。要把“点击地面让人物走过去”做对,必须拿到这条射线,再和地面平面求交。
Cocos 2.4 的cc.Camera提供了getScreenToWorldPoint(screenPos),第三个参数 z 表示距离相机的深度。取一个很小的深度(比如camera.nearClip),得到的就是射线靠近相机的那一端;理论上取远裁剪面能得到另一端,但两点构造射线和“相机位置 + 近端点”构造射线是等价的,后者更省一次反投影。
这里有一个容易被忽略的细节:相机的node.position是本地坐标,如果相机挂在某个带位移的父节点下,直接拿它当射线起点会整体偏移。正确做法是用convertToWorldSpaceAR转成世界坐标。这个问题在编辑器里预览时往往看不出来,一进游戏场景层级变深就暴露,属于典型的血泪经验。
反投影这条链路打通之后,“点哪走哪”“鼠标位置发射线”“地面指示器跟随光标”都是同一个原语的变体。区别只在于求交的对象:平面、包围盒,还是物理碰撞体。2.x 没有 3.x 那套完整的物理查询 API,手写平面求交反而是最稳的路子。
3. 移动系统的底层:输入方向、相机相对系与地面约束
3.1 输入向量为什么必须乘上相机的 yaw
键盘或摇杆给出的输入是一个二维向量:x 表示左右,y 表示前后。新手最直觉的写法是把它直接加到节点位置上,position.x += input.x * speed * dt。在
本文还有配套的精品资源,点击获取