用 matter.js 做合成大西瓜:物理引擎如何解决碰撞问题

lxf2023-05-07 00:47:07

开头长长的废话

上个周末,我用原生的 canvas api 写了一个弹球游戏:/post/713958… 。在实现 碰撞检测 时我遇到了一个小小的问题:

碰撞检测时如果只考虑小球和直线坐标是否有重叠,那么在发生碰撞后,下一帧小球速度方向改变、但小球的坐标和直线坐标仍可能有重叠,又会被判定为发生碰撞,速度方向再一次改变,反而变成了原来的方向。

用 matter.js 做合成大西瓜:物理引擎如何解决碰撞问题

我在当时想到的解决方案是,每一条直线我都认为是单向反射的:比如一条水平的直线,只有当小球 y 方向的速度分量是向下的时候,才会反弹,如果小球速度是向上的,则让它穿过。

这个解决方案显然不合理,我在之后又考虑过结合小球圆心离直线的相对位置对速度做过滤,好像又有一丝微妙地不合理……

转眼就到了新的周末,终于有一个比较整块的时间研究一下了。打算看一看比较成熟的 canvas 2d 物理引擎 matter.js 是怎么处理碰撞检测问题的。

但是作为一个初学者,直接看源码有点点吃力,所以我先用 matter.js 撸一个简易版合成大西瓜游戏,感受一下引擎库是怎么使用的。

合成大西瓜

用 matter.js 做合成大西瓜:物理引擎如何解决碰撞问题

  1. 准备 matter-js:brm.io/matter-js/i… ;

  2. 准备素材:www.wesane.com/game/654/ ,存一下 source 里的图片;

  3. 前 5 次依次出现 0 - 4号水果,其他时候等概率随机出现 0 号 - 4 号;

  4. 在屏幕正上方新建 0 号水果;

  5. 监听鼠标点击事件:

    1. 将当前水果移动到鼠标点击的水平位置,setStatic(false) 自由下落;
    2. 定时 500ms 后在屏幕正上方位置新建一个水果,将图片素材设置为纹理,setStatic(ture) 禁止下落;
  6. 监听物品碰撞事件,如果两个水果发生碰撞,则判断水果类型是否相同,如果相同,则:

    1. 两圆心为圆心,生成一个高一级的水果(10 号水果除外);
    2. 删除旧水果
    3. 加入新水果;
  7. 判断是否有水果超出顶部标准线,如果有,则绘制标准线,游戏结束,2s 后清空所有水果并重新开始游戏;

  8. matter 有一个要注意的地方:如果在屏幕正上方生成了一个水果,在点击时直接把水果位置赋值为点击位置,它会认为水果是以一个超快的速度平移过去的,而且会在后面的运动持续。必须用 Body.setPosition 的写法;

    用 matter.js 做合成大西瓜:物理引擎如何解决碰撞问题

合成大西瓜代码地址:github.com/tszbstzm/20…

matters.js 的碰撞检测

碰撞如何改变速度 (./src/collision/Resolver.js)

  1. matter.js 中物体的速度由原速度、空气阻力和物体受力决定;(./src/body/Body.js) 用 matter.js 做合成大西瓜:物理引擎如何解决碰撞问题

  2. 碰撞时,速度的改变是根据冲量计算的;

  3. 碰撞时,首先会根据 restitution、velocity 和 share 计算本次碰撞的法向冲量和切向冲量,其中:

    1. restitution 为弹性常数,由碰撞双方材质决定,取两者弹性常数的大值;
    2. velocity 为速度;
    3. share 是指冲量要如何分配,比如这次碰撞中 a 和 b 发生了碰撞,其中 b 共有 5 个支持点(support),contanctShare 就约为 1/5,share 在此基础上考虑质量、惯性和偏移; 用 matter.js 做合成大西瓜:物理引擎如何解决碰撞问题

    用 matter.js 做合成大西瓜:物理引擎如何解决碰撞问题

  4. 低速状态下,直接用加法计算碰撞后物体的新冲量,再根据冲量除以质量计算速度改变,最后根据速度修改物品预计算位置(positionPrev,并非实际位置,可以理解为一个计算中间量);

    用 matter.js 做合成大西瓜:物理引擎如何解决碰撞问题

  5. body.update,根据 position 和 positionPrev 修改速度;

    用 matter.js 做合成大西瓜:物理引擎如何解决碰撞问题

如何检测发生碰撞(./src/collision/Detector.js 和 ./src/collision/Collision.js)

  1. 预检测:检测两物体的包含框是否有重叠区域,就是比较两者在 x 和 y 方向的 min 和 max;

    用 matter.js 做合成大西瓜:物理引擎如何解决碰撞问题

  2. 计算两物品的顶点,在 a 物体的轴组和 b 物体的轴组上是否有重叠: 用 matter.js 做合成大西瓜:物理引擎如何解决碰撞问题

  3. _overlapAxes 具体实现

    1. 遍历指定轴组;
    2. 对于当前轴,遍历物品 a 的顶点,计算顶点在轴上投影的值,记录当前轴上顶点投影的最大值和最小值;
    3. 对物品 b 同样处理;
    4. 计算当前轴两者投影重叠差值,并更新轴组投影重叠最小值;
    5. 如果重叠差值已经小于 0 了,说明是非碰撞场景,直接 break;(这里不太明白???)

    用 matter.js 做合成大西瓜:物理引擎如何解决碰撞问题

结尾长长的废话

和我一开始预想得不太一样,matter.js 并没有在碰撞检测上考虑速度方向的问题,而是在计算碰撞导致的速度改变时,考虑了惯性和偏移的影响。看来如果真的要做一个物理引擎,还是要尽量地从底层模拟真实场景。

当然,matter.js 里复杂的逻辑还有很多,我只是看了一个大概的思路(而且还没有完全看懂)。

另外,我在使用时也遇到了隔板较薄的情况下,物体能够穿透的问题,不知道是 bug 还是 matter.js 有意为之。

本网站是一个以CSS、JavaScript、Vue、HTML为核心的前端开发技术网站。我们致力于为广大前端开发者提供专业、全面、实用的前端开发知识和技术支持。 在本网站中,您可以学习到最新的前端开发技术,了解前端开发的最新趋势和最佳实践。我们提供丰富的教程和案例,让您可以快速掌握前端开发的核心技术和流程。 本网站还提供一系列实用的工具和插件,帮助您更加高效地进行前端开发工作。我们提供的工具和插件都经过精心设计和优化,可以帮助您节省时间和精力,提升开发效率。 除此之外,本网站还拥有一个活跃的社区,您可以在社区中与其他前端开发者交流技术、分享经验、解决问题。我们相信,社区的力量可以帮助您更好地成长和进步。 在本网站中,您可以找到您需要的一切前端开发资源,让您成为一名更加优秀的前端开发者。欢迎您加入我们的大家庭,一起探索前端开发的无限可能!