一、碰撞检测要回答什么问题?
游戏里每一帧,都要问一句:
“这两个东西碰到了没有?”
比如:
- 子弹打中敌人没有?
- 玩家踩到地面没有?
- 角色撞到墙没有?
- 玩家走进宝箱的范围没有?
碰到之后要做什么(扣血、停下、弹开、播放音效),那是碰撞响应,是另一件事。
碰撞检测只负责回答:碰到了吗?
二、最简单的两种方法
游戏里大部分物体,其实都可以用圆或者方框来近似。
1. 圆和圆:看距离
两个圆,圆心之间的距离 ≤ 两个半径加起来,就碰到了。
没碰到: 碰到了: ( A ) ( B ) ( A )( B ) ←── 距离 ──→ 距离 ≤ 半径和例子:
- A 圆心 (0, 0),半径 1
- B 圆心 (3, 0),半径 1
- 距离 = 3,半径和 = 2
- 3 > 2 →没碰到
boolHit(Vector2a,floatra,Vector2b,floatrb){returnVector2.Distance(a,b)<=ra+rb;}适合:子弹、球、角色的大致范围。
2. 方框和方框:看左右、看上下
两个不旋转的方框(叫AABB),要同时满足:
- 左右方向有重叠
- 上下方向有重叠
只要有一个方向分开了,就没碰到。
左右重叠,上下也重叠 → 碰到 左右重叠,但上下分开 → 没碰到 ┌────┐ ┌────┐ │ A ┌┼───┐ │ A │ └────┼┘ B │ └────┘ └────┘ ┌────┐ │ B │ └────┘boolHit(Recta,Rectb){bool左右重叠=a.xMin<=b.xMax&&a.xMax>=b.xMin;bool上下重叠=a.yMin<=b.yMax&&a.yMax>=b.yMin;return左右重叠&&上下重叠;}适合:平台跳跃游戏、俯视角游戏里的墙、地块、箱子。
很多 2D 小游戏,只用这两种方法就够了。
三、东西多了怎么办?先粗筛,再细查
假如场景里有 1000 个物体,两两检测就是约 50 万次,太慢。
所以真实的游戏都分两步:
| 阶段 | 做什么 | 比喻 |
|---|---|---|
| 粗检测 | 快速排除明显离得很远的物体 | 先看是不是在同一个房间 |
| 细检测 | 对靠得近的物体,精确判断碰没碰到 | 再看是不是真的撞上了 |
粗检测常用的方法:把地图划成格子,只检测同一个格子里的物体。
┌────┬────┬────┐ │ ● │ │ │ │ ● │ │ ● │ 只有左上格子里的两个点需要互相检测 ├────┼────┼────┤ │ │ ● │ │ └────┴────┴────┘四、形状复杂了怎么办?
如果物体是旋转的方框、三角形、多边形,圆和 AABB 的方法就不够用了。这时候才需要更高级的方法:
| 方法 | 一句话 |
|---|---|
| SAT(分离轴) | 找一个方向,如果在这个方向上两个形状分开了,就没碰到 |
| GJK | 前面讲的那个,用 Support 函数,能处理任意凸形状 |
前面卡住的地方,其实属于**“进阶内容”**。不先掌握圆和方框,直接看 GJK 当然会很难。
五、游戏里常见的碰撞问题
| 问题 | 现象 | 常见解决办法 |
|---|---|---|
| 穿墙 | 子弹太快,一帧就跳过了墙 | 检测"移动路线"而不是只看位置(射线检测) |
| 卡墙 / 抖动 | 角色贴着墙一直抖 | 碰撞后把角色推出来一点 |
| 太慢 | 物体多了掉帧 | 先粗检测,用格子划分 |
六、如果你用 Unity
Unity 已经帮你做好了大部分工作,你只需要:
- 给物体加Collider(BoxCollider2D、CircleCollider2D 等)
- 至少一方加Rigidbody2D
- 写回调函数:
voidOnCollisionEnter2D(Collision2Dother){Debug.Log("撞到了:"+other.gameObject.name);}GJK、SAT 这些,引擎内部已经实现了,平时做游戏不需要自己写。