项目经理让我在 2D 项目里做 3D 音乐球球,我只好做了个“假的”

引言

哈喽大家好,我是亿元程序员,一位有着 8 年游戏行业经验的主程。

前几天,项目经理丢过来一个游戏视频:一颗球跟着音乐不断弹跳,脚下的平台从远处飞来,踩中以后放大消失,整个画面有很强的空间纵深感。

经理的要求也很直接:

把这个音乐球球玩法嵌进我们自己的项目。

我看完视频,又看了一眼手里的工程。

视频是 3D 游戏,我们的项目却是一个纯 2D 项目。

我们项目是一个纯 2D 项目,为了一个独立玩法,再加入模型、材质、灯光、3D 相机和一整套空间逻辑,改动实在有点大。

于是我很诚实地告诉经理:

“这个是 3D 效果,我们现在的项目是 2D,直接做不了。”

本以为事情到这里就结束了,没想到经理沉默几秒,又补了一句:

“那能不能用 2D 做一个?”

字越少,需求越大。

既然不能真的搭一个 3D 世界,那就只剩下一条路:

用 2D 把玩家的眼睛骗过去。

言归正传,本期文章,我们就使用 Cocos Creator 3.8.7,不用 3D 模型,不用透视相机,只靠一张背景、一个平台、一颗球和四个定位点,实现一个伪 3D 的音乐球球。

先分析一下

做一个 3D 游戏,通常需要建模、相机、灯光和碰撞。

先别急着打开建模软件。

我们把视频暂停,一帧一帧观察,会发现这个玩法真正需要表现的内容并不多。

平台部分:

  • 1. 平台从画面远处向近处移动。
  • 2. 平台越靠近镜头,看起来越大。
  • 3. 远处的跑道很窄,近处的跑道很宽。
  • 4. 近处的平台能够遮挡远处的平台。

球的部分:

  • 1. 球一直待在画面下方的固定区域。
  • 2. 平台不断向球靠近。
  • 3. 每个平台到达固定位置时,球正好落下。
  • 4. 平台被踩中以后放大并消失。

把项目改成 3D?

有的小伙伴可能就蠢蠢欲动了:为什么不直接把项目改成支持3D ?

这里先说明一下,我们不是不能使用 3D。

如果项目本身就是 3D,或者玩法需要自由旋转相机、真实空间碰撞和多角度观察,那么直接使用 3D 当然更合适。

但当前需求有几个特点:

  • 原项目是纯 2D 项目。
  • 音乐球球只是其中一个独立玩法(副玩法)。
  • 游戏镜头始终固定,不需要自由旋转。
  • 玩家只会从正面看到平台从远处靠近。
  • 为这个玩法单独准备模型、材质和灯光,开发成本偏高。

换句话说,经理要的是看起来像 3D 的效果,并没有要求我们真的建模。

既然镜头固定,玩家判断纵深主要依赖近大远小、透视收拢和前后遮挡,那么我们只要把这些视觉线索补齐即可。

资源准备

本次实战我们直接从视频里面截图,拿到我们所需要的资源。

视频里抠出来的,仅供学习

整个示例只需要三张图片:

  • background.png:游戏背景,尺寸为 750 × 1334

  • platform.png:带透明通道的平台图片。

  • ball.png:带透明通道的球图片。

背景负责提供远处的地平线、跑道和整体氛围;平台必须单独拆出来,因为后面需要不断移动、缩放和复制;也需要独立出来,用于播放弹跳效果。

Cocos Creator 中建立下面的节点结构:

各节点的职责如下:

  • background:背景节点,也是伪 3D 脚本的挂载节点。

  • platform:平台模板,运行时会复制出多个平台。

  • ball:玩家看到的发光球。

  • p:四个透视定位点的父节点。

  • p1、p2:跑道近端的左右边界。

  • p3、p4:跑道远端的左右边界。

伪 3D 的关键:透视

我们先不写代码,在背景上确定跑道的透视范围。

p1、p2 放在画面下方,分别对齐跑道近端的左右两侧;将 p3、p4 放在地平线附近,对齐跑道远端的左右两侧。

四个点会组成一个梯形:

              p3 ------ p4
                  远端
                   │
                   │
                   │
                  近端
          p1 ---------------- p2

平台会从 p3、p4 的中点出发,沿着跑道中线移动到 p1、p2 的中点。

这四个点,就是整个伪 3D 世界的轨道骨架。

“Z 轴”

2D 节点只有 xy,没有真正的空间深度。

没有条件,我们就创造条件。

为每个平台额外保存一个 depth

type PlatformItem = {
    node: Node;
    depth: number;
};

这里depth 不属于 Cocos 的坐标系统,只是我们自己定义的一个数字。

约定如下:

depth 平台状态
小于 0 还没有进入画面
等于 0 位于跑道远端
0 到 1 正在从远处向近处移动
等于 1 到达跑道近端
大于 1 继续向镜头外移动

每一帧,让这个数字不断增加:

item.depth += depthSpeed * dt;

depthSpeed 控制平台前进的速度,dt 是当前帧经过的时间。

平台节点仍然只在二维画布上运动,但有了 depth,我们就拥有了一条自己编出来的假 Z 轴。

根据 depth 计算平台位置

有了深度,接下来要解决的问题是:平台在每个深度应该放在哪里?

取得近端和远端的中点,再通过 math.lerp 计算平台当前的位置:

lerp 是线性插值,可以把它理解为:

从 A 走到 B,现在已经走了百分之多少?

depth = 0 时,返回远端位置;当 depth = 0.5 时,平台走到远端与近端的中间;当 depth = 1 时,平台到达近端。

然后把结果设置给平台:

item.node.setPosition(x, y);

这一步完成以后,平台已经能够从地平线移动到画面下方,但它看起来更像一张大小不变的贴纸在屏幕上滑动,距离 3D 还差一点意思。

平台还需要近大远小

人眼判断物体远近,一个非常重要的依据就是大小。

同一个物体在远处看起来小,在近处看起来大。

因此,平台的位置由 depth 控制,缩放也必须由同一个 depth 控制:

缩放

例如:

  • farScale = 0.12:平台刚进入远端时只有正常尺寸的 12%。

  • nearScale = 1:平台到达近端时恢复到正常大小。

直接使用线性缩放已经能看到纵深效果,不过变化会稍微有些机械。

我们可以先对深度做一次平滑处理:

const t = math.clamp01(depth);
const smooth = t * t * (3 - 2 * t);
const scale = math.lerp(farScale, nearScale, smooth);

这条曲线会让平台在远处的变化相对平缓,靠近镜头后放大更加明显,视觉上比完全匀速缩放更自然。

这里需要注意一个原则:

平台的位置和大小必须由同一个 depth 控制。

如果平台的位置还在远处,大小却已经接近屏幕宽度,玩家会立刻发现这不是透视,只是一张图片在乱跑。

效果如下:

让球的弹跳与平台同步

平台已经能正常移动,接下来轮到球了。

最直接的做法,是给球播放一个无限循环的上下移动动画。

这样做有一个问题:球的动画和平台的移动互不相关。

只要运行时间稍长,或者修改了平台速度,球就会逐渐和平台错开,最后非常认真地踩在空气上。

因此,球的弹跳不能拥有自己的节奏。

平台什么时候到达落点,决定了球什么时候落下。

我们先定义一个 ballContactDepth,表示球踩中平台时,平台所处的深度。

然后找出下一块即将经过这个落点的平台:

image

再根据平台深度计算当前弹跳进度:

phase 会从 0 逐渐增加到 1

接着使用正弦函数计算跳跃高度:

image

刚好能形成一条拱形曲线:

phase jump 球的状态
0 0 从平台起跳
0.5 1 到达最高点
1 0 落到下一块平台

最后把跳跃高度加到球的基础位置上:

ball.setPosition(x, baseY + jump * jumpHeight);

因为 phase 来自平台的深度,所以修改 depthSpeed 后,球的弹跳节奏也会自动跟着改变。

修改 depthGap 后,平台间距和球的弹跳周期也会一起改变。

加入平台踩踏反馈

现在球已经能按节奏落到平台上,但如果平台只是继续从球下面穿过去,玩家仍然很难感受到“踩中”。

因此,平台到达接触深度以后,再加入一个很短的踩踏动画:

现在看起来就不再是平台从球脚下路过,而是球真正踩中并击穿了平台。

再进一步

本期只演示了最基础的中线平台和自动弹跳。

基于现有结构,还可以继续扩展:

  • 增加左、中、右三条轨道。
  • 使用点击或滑动控制球换轨。
  • 根据音乐 BPM 控制平台生成节奏。
  • 增加 PerfectGoodMiss 判定。
  • 增加分数和连击系统。
  • 制作普通平台、加速平台和特殊平台。
  • 增加球的拖尾、落地粒子和镜头震动。
  • 根据歌曲配置动态生成平台序列。

尤其是音乐节奏部分,下一步不应该继续使用固定的 depthGap,而是根据歌曲的节拍时间计算每个平台的初始深度。

这样平台到达落点的时刻,才能真正与音乐鼓点对齐。

结语

以上就是在 2D 项目中实现伪 3D 游戏效果的开发经验,不知道小伙伴们有没有更好的思路?评论区告诉大家。


我是"亿元程序员",一位有着8年游戏行业经验的主程。在游戏开发中,希望能给到您帮助,也希望通过您能帮助到大家。

实不相瞒,想要个爱心!请把该文章分享给你觉得有需要的其他小伙伴。谢谢!

推荐文章:

亿元Cocos小游戏实战合集2.0

亿元Cocos小游戏实战合集1.0

老板说最近这款游戏很火让我抄,可是我连玩都玩不明白…

这款值68亿的游戏,你不实战一下吗?安排!

小伙伴说我的拼图游戏用Mask不能合批…

俄罗斯方块谁不会做…啊?流沙版?

最近很火的一个拼图游戏,老板让我用Cocos3.8做一个…

老板说拼图游戏太卷了,让我用Cocos做个3d版本的…

敢不敢挑战用Cocos3.8复刻曾经很火的割绳子游戏?

3赞

有点强,放下周一再看