
引言
哈喽大家好,我是亿元程序员。
有个小伙伴发给我一段视频,是一个解压游戏:一个方方正正的快递盒,上面贴着几条封箱胶带,点哪条,哪条就从边上卷起来消失:
我当时就觉得这个效果,挺有意思!
然后我就打开了Cocos Creator……
然后就踩了不少坑。
不过最终效果还挺不错的,今天就带大家完整走一遍,看看一个3D撕胶带效果是怎么在Cocos从零搭起来的。
本文源工程在文末获取,小伙伴们自行前往。
先看效果
效果就是这样:
- 拖拽可以360°旋转正方体
- 胶带可以跨越多个面(比如从正面绕到侧面)
- 点击胶带,从你点的那头开始卷,带着一个小圆柱滚轴
- 卷完就消失
话不多说,直接开干。
第一步:搭场景
在Cocos Creator里新建一个3D项目,场景很简单,核心层级只有这几个节点:
Scene
├── Main Light ← 打一盏平行光,别让正方体全黑
├── Main Camera ← 斜上方俯视,位置 (-10, 10, 10)
└── CubeRoot ← 关键!所有旋转都作用在这里
├── Cube ← 内置正方体,scale 改成 (3,3,3)
└── TapeStrips ← 所有胶带的容器节点
这里有个小细节值得说一下:
为什么要多一个 CubeRoot,不直接在 Cube 上旋转?
因为胶带也要跟着转。
如果直接旋转 Cube,胶带节点不是 Cube 的子节点,就会留在原地不动,立方体转走了,胶带还飘在空中,很尴尬。
把 Cube 和所有胶带都挂在 CubeRoot 下,旋转 CubeRoot,全部一起动,省心。
第二步:拖拽旋转(踩坑警告)
新建 CubeRotate.ts,挂在 CubeRoot 上。
思路很直接:监听鼠标拖拽 → 计算每帧位移 → 转成旋转叠加上去。
第一版我这么写的:

跑起来一看……
斜向拖的时候,感觉不对,像在跟自己较劲。
原因是:左右拖死死绑在世界Y轴,上下拖死死绑在世界X轴,完全无视当前正方体的朝向。
正确的旋转方式叫Trackball(轨迹球)。
想象你的屏幕后面有个大球,手指贴在球面上,往哪拨球就往哪转——旋转轴始终垂直于你的拖拽方向。
推导也不复杂:
拖拽向量:(dx, dy)
垂直方向:(-dy, dx) ← 数学上逆时针旋转90°
映射到世界空间:
旋转轴 = -dy × 相机右方向 + dx × 相机上方向
代码改成这样:

改完之后:
流畅多了,斜拖也完全正常。
小知识:四元数左乘 vs 右乘
next = rot × cur(左乘)→ 在世界空间叠加旋转,轴不随物体转动
next = cur × rot(右乘)→ 在局部空间叠加旋转,轴跟着物体走做Trackball用左乘,因为旋转轴是相机空间的,属于"世界视角"。
第三步:画胶带
这是整个项目最有意思的部分。
胶带不是一张图贴上去,而是用代码生成的 3D 网格。
1.胶带的本质:Quad Strip
每条胶带在代码里先定义一条"中线"(若干个控制点,不知道大家是否还记得绳子纹理那期),然后向两侧各展开半个宽度,得到左右两列顶点,连成三角形条带:
中线: P0 ──── P1 ──── P2
展开: L0 L1 L2 ← 左边缘
R0 R1 R2 ← 右边缘
三角形:(L0,R0,L1), (L1,R0,R1), ...
关键是怎么算"向两侧展开的方向":

这里normal是胶带所在面的朝外法线,_dir是沿中线走向的切线。叉积得到的向量一定在面的平面内,且垂直于走向——这就是胶带的宽度方向。
2.跨面胶带怎么处理?
跨面的胶带是这个项目最难的几何问题。
比如这条从正面绕到右面的胶带,我在棱的位置手动加了两个"转折点",同时切换法线方向:

两个转折点坐标只差了一个 D(0.002),中间生成一个极短的过渡quad,视觉上完全看不出缝隙。
3.最终效果如下
第四步:点击卷起(最硬核的部分)
胶带是自定义网格,引擎没有现成的点击事件,需要自己做点击检测。
1.怎么知道点到哪条胶带了?
用射线-三角形求交。
原理:从相机位置,穿过鼠标点击的屏幕坐标,发出一根射线,看这根射线和哪条胶带的三角形相交。
三步走:
第一步,用相机把屏幕坐标转成世界空间射线:

第二步,把射线变换到 CubeRoot 的局部空间。
**为什么要变换?**因为胶带顶点是在 CubeRoot 局部空间定义的,正方体旋转后,世界坐标变了,但局部坐标没变。射线和三角形必须在同一个坐标空间里才能做运算。

第三步,用算法对每个三角形做射线求交:

遍历所有三角形,有一个相交就判定命中。
2.从哪头开始卷?
靠近哪头就从哪头卷。
计算点击射线到胶带两个端点的距离:

哪头更近就从哪头卷:

3.让胶带逐渐变短
每帧根据动画进度,截取剩余的中线,重新生成网格:

4.卷轴圆柱
圆柱要跟着剥离点走,并且越卷越大。
圆柱网格只创建一次,用 setScale 来控制实际尺寸。
这样每帧只需改scale和position,不需要重建网格:
最终效果
所有问题解决之后,效果如下:
旋转流畅、跨面胶带自然、点近头从近头卷、圆柱跟随移动。
代码结构回顾
整个项目只有 4 个脚本文件,职责非常清晰:
| 文件 | 职责 | 一句话说清楚 |
|---|---|---|
CubeRotate.ts |
输入 → 旋转 | 监听拖拽,Trackball算法旋转CubeRoot |
TapeGeometry.ts |
数据 → 几何 | 纯函数,中线控制点 → 三角形顶点数组 |
TapeManager.ts |
配置 → 场景 | 定义每条胶带的形状和颜色,创建节点 |
TapeStrip.ts |
交互 → 动画 | 点击检测 + 卷起动画 + 圆柱滚轴 |
整体数据流:

结语
不忘初心,保持更新。
这个Demo虽然功能简单,但涉及到了3D游戏开发中几个非常通用的技术点:
-
Trackball旋转(任何需要拖拽旋转
3D物体的场景都能用) - 程序化Mesh生成(胶带、绳子、轨迹线、流体表面都是这个思路)
-
射线-三角形求交(
3D点击检测的通用方案) -
坐标空间变换(这是理解
3D编程的核心概念)
那么问题来了:如果要做成完整的游戏,纸皮效果、关卡编辑,应该怎么做? 欢迎评论区聊聊。
本文完整工程源码可通过点赞后私信发送"撕胶带"获取。
我是"亿元程序员",一位有着8年游戏行业经验的主程。在游戏开发中,希望能给到您帮助,也希望通过您能帮助到大家。
实不相瞒,想要个赞和在看!请把该文章分享给你觉得有需要的其他小伙伴,谢谢!
推荐文章:







