我最初把这件事想简单了。看到 2D 合成大西瓜火了之后,我决定用 Cocos Creator 3.8 加 TypeScript 做一个 3D 版。本以为就是把 2D 的圆形贴图换成 3D 球体模型,再把 2D 物理引擎换成 3D 物理引擎,剩下的逻辑复制一遍就行。真正动手之后才发现,从 2D 到 3D,不是简单多了一个 Z 轴,而是整个游戏的规则、手感、碰撞判定甚至玩家心理预期都要重新设计。
这篇文章不打算把完整代码逐行贴出来——那种教程你已经见过很多了。我更想讲清楚的是,用 Cocos Creator 3.8 做 3D 合成大西瓜时,真正值得花时间的点在哪里,哪些地方容易误判,以及怎样从零开始搭出一个可玩、稳定、能继续扩展的原型。如果你是第一次做 3D 游戏的小白,按照这条路走,会比直接搜“完整源码”更有效。
1. 先想清楚:3D 版合成大西瓜的难点根本不在建模
1.1 从 2D 到 3D,物理规则发生了质变
2D 合成大西瓜的物体基本在一个平面内堆叠,玩家操作的也是从顶部往下掉一个水果。你看到的碰撞、堆叠、滚动,其实都是二维平面上的运动。很多 2D 逻辑可以直接用圆形碰撞体去近似,感觉已经很接近“合成”这个玩法的核心了。
但到了 3D 空间,物体不再只在一个方向堆叠。球体可能从边上滚下去,可能碰撞后弹到更远的位置,可能几个同类水果只是快速擦边接触,也可能因为堆叠不稳定导致长时间抖动。这意味着,你不能简单地把碰撞检测写成“两个同类水果一碰就合成”。
3D 版合成大西瓜真正的难点是:
- 如何控制水果掉落后的最终位置,让它不会因为碰撞角度太随机而显得不公平。
- 如何让堆叠保持稳定,而不是像弹珠一样到处乱飞。
- 如何设计合成判定,既能及时响应,又不会因为瞬时碰撞、二次回调、同时合成多个而产生重复结果。
- 如何让玩家用鼠标或手指在一个 3D 视角下精准控制投掷位置,而不是凭感觉乱扔。
这些问题里,没有一个是“换个 3D 模型”能解决的。它们涉及到物理参数、碰撞监听、输入映射和游戏状态的全局设计。
1.2 为什么 Cocos Creator 3.8 是适合小白的版本
Cocos Creator 3.8 对做这个项目来说,最大的优势是“内置项足够多”。物理引擎、3D 场景编辑器、TypeScript 支持、UI 系统、动画系统,都不需要额外引第三方库。你只要创建一个 3D 模板项目,就能直接往场景里放 Cube、Sphere 这类基础几何体。
这里要先说明:我不是说 3.8 的物理系统比别的引擎强,而是说它的学习成本相对适合从 2D 转 3D 的开发者。3.x 版本把 2D 和 3D 统一到一个编辑器中,你不用切换到另一个工程去处理 3D 资源。对一个第一次做 3D 游戏的小白来说,少一个工具切换步骤,就能少踩很多环境坑。
还有一点很关键:3.8 的 TypeScript 支持已经比较成熟。你用熟悉 TS 的方式定义数据、管理状态、监听事件,而不是被编辑器绑定死。这样后续做对象池、配置表、计分逻辑,都会顺很多。
1.3 动手前先划清项目边界
很多新手一上来就找一堆西瓜、猕猴桃、苹果的高精度模型,结果光是模型资源就导入了半天,最后卡在模型格式、材质、骨骼动画上,连物理都还没跑起来。
我建议做 3D 合成大西瓜时,第一阶段统一用编辑器自带几何体:Sphere 当作水果,Box 当作容器边界。你先把玩法跑通,确认“投掷—碰撞—合成—计分—结束”这条链路是完整的,再考虑替换美术模型。
第一阶段可接受的标准是:
- 点击屏幕某个位置,水果能稳定从顶部落下来。
- 相同等级水果碰撞时能合成,并且合成结果只出现一次。
- 不同等级水果碰撞后不会误合成。
- 容器边界能挡住水果,不让它掉出屏幕。
- 水果堆叠太多,溢出容器顶部后,游戏能正确结束。
如果第一版就能做到这五条,你已经有资格说“我会用 3D 物理做合成玩法了”。建模、特效、音频,都是后话。
2. 搭建一个最小可玩原型:从场景、物理到输入
2.1 新建项目和场景基础结构
打开 Cocos Dashboard,选择 Cocos Creator 3.8 新建项目时,注意选“3D”相关模板。很多教程里用的是 2D 模板或空白模板,这会导致初始化场景里没有默认的 3D 摄像机、光照等节点,新手容易在第一步就开始怀疑自己的工程坏了。
新建完项目后,场景里通常会有 Main Light、Main Camera 这样的基础节点。如果没有,需要手动创建一个方向光和一个透视摄像机。
场景结构建议保持简单:
Canvas / UI 节点(用来显示分数和下一个水果预览) GameRoot(空节点,挂游戏管理脚本) Ground(一个 Box 或 Plane,作为地面) LeftWall / RightWall / BackWall(容器边界) SpawnPoint(投掷点,实际运行时限制 X 坐标即可)关于地面和墙壁,有两点很容易搞错。第一,边界物体不需要挂刚体,只要挂 BoxCollider 就能参与碰撞。第二,如果地面也挂了刚体,并且物理系统默认把所有刚体都当成动态物体,就会出现地面自己塌下去或物体之间互相推挤的诡异现象。静态障碍物用 Static 刚体或者不挂刚体只挂碰撞体,动态水果用 Dynamic 刚体,这是 3D 物理最基本的物体分类。
2.2 刚体和碰撞体的参数不是默认就能用
Cocos Creator 3.8 的物理系统里,刚体组件负责动力学模拟,碰撞体组件负责形状和碰撞检测。新建一个水果节点时,最常见的配置是:
- RigidBody 组件,Type 设为 Dynamic。
- SphereCollider 组件,Radius 根据水果半径调整。
- Collider 的 Material 设置摩擦力、弹性系数。
到这里还没完。真正影响手感的是刚体上的质量、线性阻尼、角度阻尼,以及碰撞体材质里的摩擦力。
新手最常犯的错误是所有水果用同一个默认质量。这会导致一个大西瓜被一个小葡萄轻轻一碰就飞走,因为你没做质量区分。从工程经验看,等级越高、体积越大的水果,质量应该按比例提升,而不是线性提升。比如最小水果质量是 1,下一级可以是 2,再下一级可以是 4 或者 8。质量大的一方在碰撞中更稳定,也更符合“大水果更重”的直觉。
线性阻尼可以适当给一点,否则球体会在容器内部弹跳很久才停下来。但别给太高,否则堆叠时会显得很僵硬。角度阻尼同理,它决定水果滚动时的活泼程度。做合成大西瓜这类“堆叠”玩法,我一般会优先保证堆叠稳定性,滚动效果排在第二位。
2.3 输入控制的核心是“屏幕坐标到 3D 坐标”
3D 合成大西瓜里,玩家看到的是一个 3D 场景,但他的操作输入是屏幕上的二维坐标点。你需要把这些坐标映射到 3D 世界的某个平面或某条直线上。
常见做法是:从摄像机生成一条射线,射线和投掷平面求交点。在 Cocos Creator 3.8 中,可以用摄像机的screenPointToRay这类接口生成射线,然后和水平平面求交。这个水平面通常设置在容器顶部的某个高度上,水果生成后会从这个位置开始掉落。
这里有一个新手很容易忽略的问题:摄像机是透视还是正交,直接影响点击映射的精准度。透视摄像机下,你看到的屏幕边缘对应 3D 世界里的斜向射线,所以点击屏幕左右边缘时,实际落点会比视觉上更偏。如果用正交摄像机,屏幕坐标和世界坐标的映射更直观,但透视摄像机带来的立体感更强,后期做特效和相机动画也更方便。
我的建议是如果你第一次做,可以用正交摄像机先把流程跑通,之后再改透视。因为正交摄像机的调试理解成本更低,也更容易判断容器边界是否和屏幕边缘对齐。等核心玩法稳定后,再换成透视,并配合一条“投掷预览线”来降低玩家的操作误差。
在输入限制上,不要允许玩家把水果投到容器外。正常情况下,你只需要把屏幕坐标映射后的 X 值限制在左右边界之间即可。
3. 核心玩法:碰撞监听、合成判定与水果升级
3.1 碰撞监听:onCollisionEnter 还是 onTriggerEnter
Cocos Creator 3.8 物理系统里,碰撞体之间的交互一般有两种:物理碰撞和触发器。物理碰撞会让刚体产生真实的阻挡和弹跳;触发器则只负责检测重叠,不影响物理运动。
做合成大西瓜时,水果与水果之间应该用物理碰撞,也就是要产生真实的堆叠效果,所以不能用 Trigger。只有当你做特效范围检测、采集区域这类不关心物理阻挡的逻辑时,才适合用 Trigger。
但这里有个比较常用的技巧:如果你希望减少物理回调的复杂度,可以先用碰撞监听事件拿到刚体之间的接触数据,触发时再判断是否同类水果、是否已经参与过合成。不要一边开着物理碰撞,一边又用 Trigger 再去重复检测一遍。
在 Cocos Creator 3.8 里,常见写法是在水果脚本的onCollisionEnter回调里拿到另一个碰撞体,再读取对方节点上的水果等级脚本。一个容易踩的坑是回调里不能立刻销毁节点。因为物理引擎在碰撞回调期间可能还在处理同一批接触,立即销毁会产生奇怪的报错或物理状态不一致。更稳妥的做法是:先把所有待合成的节点记录下来,延迟到下一帧或短时间后进行处理。
3.2 合成判定:防止重复合成是重灾区
两个相同等级的水果碰撞后,很自然会想到:直接销毁这两个节点,在它们中间生成一个下一级水果。但实际落地时会出现几种麻烦:
第一种,同一帧里,两个水果可能同时收到多次碰撞回调。如果不加标记位,它们会被合成两次。解决办法是在水果节点上增加一个isMerging标记,一旦进入合成流程,立刻置为真,后面的回调直接忽略。
第二种,两个水果碰撞后,其中一个已经在和另外的同类水果接触。此时如果只考虑“两两接触”,可能出现多个水果同时参与合成,逻辑会乱。更稳健的判定方式是,当发生碰撞时,只允许这两个物体中等级最低且未被标记的节点参与合成,等待一帧后再次确认,再执行销毁。
第三种,合成后的新水果生成位置如果直接取碰撞点,可能嵌入地面或其他物体,导致新水果一出生就抖动或者被挤出容器。我一般会把生成位置取两个水果中心的平均值,并向上偏移一点,这样新水果是从上方落下来的,而不是从两个旧水果中间炸出来。
合成判定的状态流程可以写成:
- 碰撞回调里检查两节点是否都有水果数据。
- 检查两节点水果等级是否相同。
- 检查两节点是否都未被标记为合成中。
- 标记两个节点为合成中。
- 延迟一帧或短时间后执行销毁。
- 生成下一级水果,播放特效和音效。
这样能覆盖绝大多数情况。如果还有“三个同等级水果同时接触”的情况,可以在脚本的更新循环里增加一个扫描逻辑:遍历所有存活水果,找出距离小于一定阈值的同等级水果。阈值可以设成两个半径之和乘以 0.8 这样的经验值,而不是完全依赖碰撞回调。这个方案要多花一些性能,但合成判断会明显更稳。
3.3 水果等级、分数和游戏结束条件
水果不要用“每个节点单独写一个脚本”的方式管理等级。更好的做法是维护一个配置表,每一项包含等级、模型名、缩放、质量、分数、下一级模型等。在 TypeScript 里可以定义一个数组或者类,实例化水果时直接从配置表读取参数,而不是手写一堆 if else。
如果你现在正在用 TS 管理水果列表,一个容易见到的场景是频繁 push 和 splice 数组元素。这里要特别注意:销毁节点后,要同步从数组里移除对应的引用,否则数组越摆越多,游戏会越来越卡。
分数可以按合成后新水果的等级来计算。比如合成出 Level 1 得 10 分,Level 2 得 20 分,等级越高分数曲线越陡。想让玩家爽,就把分数写成分段函数或指数增长曲线;想让数值曲线平稳,就保持线性增长。
游戏结束条件,一般不能简单用“水果数量超过多少”来判断。因为不同水果体积不同。我习惯的做法是,在容器顶部设定一个水位线,当某个水果的 Y 坐标高于这条线并持续超过一定时间,就触发游戏结束。要加“持续一定时间”这个条件,是因为水果掉落瞬间可能因为弹跳短暂超过水位线,不代表真的已经溢出。
4. 新手最容易踩的坑:物理参数、性能与调试链路
4.1 物理参数的暗坑:刚体像弹簧、水果卡死、穿透
3D 合成大西瓜最容易出现的物理问题是“水果堆不稳定”。如果你把容器的地面和墙壁都做成很弹的材质,水果落下来后就很难停下来,整个堆叠会像弹簧一样不断抖动。做合成玩法时,地面、墙壁的摩擦力可以高一些,弹性系数最好接近 0,否则每次水果落到地面都会反弹,玩家会明显感觉到“手感不对”。
第二个常见问题是“水果卡在墙缝里”。容器角落通常是左墙、右墙、后墙、地面四个 BoxCollider 交界的区域,如果碰撞体之间没有留出一丁点缝隙,球体可能会被反复挤压,出现震动或卡住。做静态容器时,可以适当让墙壁之间留一点点微小间隙,或者使用更简单的单个 Box 做成“碗”字形,而不是用多个平面拼角。
第三个问题是“水果穿透”。物理引擎本身是为了速度效率设计的,当一个很小的水果以很高速度撞到很薄的墙,有可能出现穿透。解决办法不一定是提高物理精度,而是先把墙体厚度加大。例如用 Box 做墙时,厚度至少要有水果半径的一半以上,否则很容易发生极端帧下的穿透。
第四个问题是“刚体睡着了”。Cocos Creator 物理引擎通常有休眠机制,物体速度很低时自动休眠,这能省性能。但合成玩法中,如果水果被压住但还没完全静止,过早休眠会导致它不再响应新的碰撞,看起来像被冻结了。如果你发现水果在堆叠中卡住不动,排查方向之一就是刚体的休眠阈值和允许休眠设置。
4.2 性能压力:对象池和数组管理是最重要的工程手段
一个长时间不合成的大西瓜局,场景里可能同时存在几十个水果节点。如果每次创建都用instantiate,每次回收都用destroy,那么频繁的节点创建销毁会产生大量 GC,画面就会周期性卡顿。
解决方式是用对象池:预先创建好一批不同等级的水果节点,需要时从池里取,不需要时回收到池里。对象池对提升稳定性的效果非常明显,而且实现也不算复杂。你可以用一个 PoolManager 管理多个节点池,每个池对应不同水果等级。取用时重新设置位置、缩放、质量和碰撞状态;回收时停止所有物理运动,隐藏节点。
除了对象池,还要管理好存活水果的数组。不要在更新循环里频繁find或getChildByName去查找节点,应该在生成水果时就把它注册进数组,销毁时从数组移除。数组中不需要的元素要及时清除,否则随着对局时间变长,遍历开销会越来越大。
还有一点容易被忽略:阴影。3D 场景打开阴影后,几十个动态物体互相投射阴影,GPU 压力会明显上升。对于新手 Demo,可以把阴影关掉或者只让最主要的光源产生一次阴影,先把帧率稳住。
4.3 排查链路:从现象反推问题在哪一层
如果你做了以后发现水果不合成、或者合成了但不计分、或者水果直接穿墙,不要一上来就怀疑引擎有问题。按下面这个顺序排查,90% 的问题都能定位:
- 看物理系统是否启用。Cocos Creator 的 3D 物理系统如果没开启或在错误的模块配置下,刚体不会运动。先确认最简单的球体能从高处掉落。
- 看碰撞体是否生效。选中水果节点,检查 RigidBody 和 Collider 是否存在、是否被禁用,以及 Group 和 Mask 是否允许两个物体互相碰撞。如果物件的菜单里只勾选了“自身分组”,没有勾选“能碰撞的分组”,即使两个物体距离再近也不会有回调。
- 看回调本身是否被触发。在 onCollisionEnter 里打印日志,观察是否真的进入了这个函数。如果没进来,问题在物理设置或分组;如果进来了但合成没发生,问题在状态标记或逻辑顺序。
- 看合成执行顺序。当出现重复合成或合成结果异常,重点检查是否在销毁节点前清理了监听,是否在回调里直接销毁了节点,是否遗漏了对
isMerging标记的判断。 - 看数据是否同步。如果水果数组里还留着已被销毁的节点引用,后续遍历时就会报空指针或影响统计。排查时可以直接打印数组长度,看它是否一直增长。
- 看性能是否触发卡顿。如果帧率很低,优先关阴影、改对象池、减少 draw call;如果只是偶尔卡一下,优先检查 GC 和数组操作。
这套顺序几乎适应所有物理类玩法项目。先确定是哪一层坏了,再决定修哪里,不要像无头苍蝇一样改参数。
5. 从 Demo 到完整可玩:一份可复用的迭代框架
5.1 四阶段迭代法:先跑通,再优化,最后工程化
做 3D 合成大西瓜这类项目,如果一开始就想着做到“完美”,很容易烂尾。我用下来比较顺的节奏是四个阶段:
- 阶段一:白模把手。场景里只有地面、墙壁和几个碰撞体,目标是投掷、掉落、碰撞、堆叠、游戏结束,这条链路全程能跑通。
- 阶段二:核心玩法。加入合成判定、水果等级、计分、下一个水果预览。这个阶段要确保合成不会出现重复或漏判。
- 阶段三:工程优化。加入对象池、物理参数调优、UI 适配、音效、简单的投掷预览线。这个阶段解决“能不能长时间玩不卡”的问题。
- 阶段四:内容包装。替换成美术模型,加入特效、相机动效、更多水果等级和场景氛围。这个阶段才需要投入大量资源。
每个阶段都有明确的验收标准。不要在没有完成阶段二的情况下直接进入阶段四,因为一旦后续逻辑改动,前面的美术资源也会跟着返工。
5.2 长期使用还需要补什么
如果你的目标不是做一个一次性 Demo,而是想发布到微信小游戏或其他平台,那么还需要补几块:
- 异步资源加载:水果模型、贴图、音频不要全部打进首包,尤其是小游戏平台有包体限制。可以考虑按等级加载,或用远程资源包。
- 多分辨率适配:手机上不同宽高比会明显影响容器边界。你需要在启动时根据屏幕宽高比重新计算容器的缩放和地面位置。
- 存档和排行榜:如果要接排行榜,分数上报、用户数据存储、防作弊校验都要单独考虑。
- 埋点和日志:可以记录玩家在第几回合失败、平均堆叠高度、点击位置分布,这些数据对调整手感很有帮助。
- 代码结构:建议把游戏状态、水果配置、对象池、音效管理、UI 管理拆成独立模块。不要把所有逻辑写在一个巨型脚本里,不然改一个功能就要重新读一遍几百行文件。
这些点不会在“最小原型”里体现,但当你把项目推进到可发布状态时,每一块都会变成刚需。
5.3 什么情况下不建议用这套方案
我也要泼一盆冷水。如果你需要的不是“玩法像合成大西瓜”,而是“超高精度物理模拟、数千个刚体同时运动、多玩家同屏堆叠”,Cocos Creator 内置物理未必是最优解。那种场景下,你可能需要先评估更好的物理引擎、自定义碰撞体拆分、网络同步方案,而不是直接套用本文框架。
另外,如果你对 TypeScript 还非常不熟,写这种带大量异步回调的玩法逻辑会比较痛苦。可以先花一点时间把数组操作、类型定义、事件回调、类与继承这几个基础点补上,再开始动手。不是说不可以边做边学,而是尽量先了解一些小知识点,避免遇到问题时连报错都读不懂。
我的总体判断是:用 Cocos Creator 3.8 做 3D 合成大西瓜,最大的收获不是“做了一个 3D 游戏”,而是理解了一个通用原则——当你把一个成熟的 2D 玩法搬到 3D 空间时,最值得重新设计的不是视觉表现,而是物理规则和交互手感。只要你能在白模阶段把物理、碰撞、合成判定和输入控制稳定下来,其他内容都是锦上添花。
现在最该做的,不是继续找模型和皮肤,而是打开 Cocos Creator 3.8,创建项目,放一个球体,挂上刚体,让它从空中掉下来。先确认它落地了,你的旅程才算真正开始。