Plinko 编程挑战 Part 2:用 Matter.js 为物理模拟添加边界与底部分格篮筐 教程文档示例工程【免费下载链接】website-archiveArchive of the Coding Train website (first version)项目地址https://gitcode.com/gh_mirrors/we/website-archive点击查看免费下载本文基于 Coding Train 网站的 Plinko 编程挑战第 62.2 部分CC_062_plinko示例展开讲解在已有粒子下落模拟的基础上如何完成三件事为画布添加边界墙、删除飞出屏幕的刚体body、在底部构造粒子落位的篮筐分格。读完本文你可以理解 Matter.js 中静态墙体与动态刚体的构建方式、物理世界的刚体生命周期管理World.add/World.remove以及碰撞事件的 label 匹配机制并能直接打开仓库中的示例代码在本地运行。1. Part 2 的任务定位原始文档 062.2-plinko.md 对该部分的核心描述是为草图添加边界boundaries、删除屏幕外的刚体delete offscreen bodies并在底部加入供粒子落位的篮筐buckets。它是四部分系列中的第二部分Part 1062.1-plinko.md用 Matter.js 物理引擎 p5.js 绘制建立 Plinko 物理模拟的基础Part 2本文主题边界、离屏刚体清理、底部篮筐Part 3062.3-plinko.md加入随机性试验图钉尺寸、颜色与物理参数Part 4062.4-plinko.md加入音效粒子每次撞到图钉时播放铃声。对应源码位于 CodingChallenges/CC_062_plinko/P5/ 目录文件职责index.html页面入口按顺序加载 p5.js、p5.sound、Matter.js 与四个脚本sketch.js主逻辑setup/draw、碰撞事件、生成与清理粒子particle.jsParticle动态圆刚体含离屏判定plinko.jsPlinko静态图钉刚体boundary.jsBoundary静态矩形墙体刚体ding.mp3铃声素材preload中加载当前源码中播放被注释2. 依赖与运行方式index.html 通过 CDN 加载三个库再依次引入四个本地脚本script srchttps://cdn.jsdelivr.net/npm/p51.4.1/lib/p5.min.js/script script srchttps://cdn.jsdelivr.net/npm/p51.4.1/lib/addons/p5.sound.min.js/script script srchttps://cdnjs.cloudflare.com/ajax/libs/matter-js/0.12.0/matter.min.js/script script srcsketch.js/script script srcparticle.js/script script srcplinko.js/script script srcboundary.js/script版本前提p5.js 1.4.1含 p5.sound 插件与 Matter.js 0.12.0。本地运行时在CodingChallenges/CC_062_plinko/P5/目录下启动一个静态 HTTP 服务如python -m http.server后在浏览器打开index.html即可由于 sketch.js 的preload()中使用了loadSound(ding.mp3)走file://协议加载音频容易失败因此推荐用 HTTP 方式访问。从源码结构看这些文件没有使用 ES module而是通过全局作用域协作sketch.js顶层声明的全局变量world、particles、plinkos、bounds以及 Matter.js 解构出的Engine、World、Events、Bodies会被后续脚本中的构造函数直接引用例如 boundary.js 中的World.add(world, this.body)。因此index.html中的引入顺序只需保证 p5 与 matter 先于四个脚本加载即可。3. 三种刚体构建参数与职责本示例中的物理世界由三种刚体构成各自通过不同的Bodies选项区分行为类形状Matter 选项label角色ParticleBodies.circle(x, y, r)restitution: 0.5, friction: 0, density: 1particle从顶部下落、可被模拟推动的动态粒子PlinkoBodies.circle(x, y, r)restitution: 1, friction: 0, isStatic: trueplinko静止图钉粒子在其上弹跳BoundaryBodies.rectangle(x, y, w, h)isStatic: true未设置静态墙体地面与分格隔板关键源码// particle.js节选 function Particle(x, y, r) { this.hue random(360); var options { restitution: 0.5, // 恢复系数决定碰撞后的反弹强度 friction: 0, // 无摩擦 density: 1 // 默认密度 }; x random(-1, 1); // 初始 x 加 ±1px 抖动避免多个粒子完全重叠 this.body Bodies.circle(x, y, r, options); this.body.label particle; this.r r; World.add(world, this.body); // 加入物理世界 }// plinko.js节选 function Plinko(x, y, r) { var options { restitution: 1, // 完全弹性反弹让粒子几乎不损失能量地弹开 friction: 0, isStatic: true // 静态体不参与动力学位置永不被模拟改变 }; this.body Bodies.circle(x, y, r, options); this.body.label plinko; this.r r; World.add(world, this.body); }几个值得注意的参数选择粒子restitution: 0.5而图钉restitution: 1Matter.js 中一对碰撞体的最终弹性取两者组合的结果图钉设为 1 让粒子撞钉时反弹更“脆”更接近 Plinko 棋盘上木钉的物理手感isStatic: true静态体不参与位置/速度求解因此图钉和墙体数量多本例 120 个图钉 14 面墙也不会带来等价的求解开销x random(-1, 1)粒子每 20 帧在同一个固定坐标 (300, 0) 生成见下文若不做抖动相邻粒子将沿完全相同的路径下落视觉上“排队”。这一行是后续 Part 3 系统性地加入随机性的起点。4. 边界与底部篮筐的几何构造这是 Part 2 文档所述“add boundaries”和“add buckets”的直接实现全部位于 sketch.js 的setup()中。4.1 地面var b new Boundary(width / 2, height 50, width, 100); bounds.push(b);地面是一块width × 100的矩形中心位于height 50画布 700px 高的情况下即 y 750因此其上表面恰好落在画布底边 y 700向下延伸到画布外。粒子落在地面后即停驻在篮筐底部。4.2 分格隔板构成篮筐for (var i 0; i cols 2; i) { var x i * spacing; var h 100; var w 10; var y height - h / 2; var b new Boundary(x, y, w, h); bounds.push(b); }其中cols 11、spacing width / cols 600 / 11 ≈ 54.5sketch.js 第 21-22 行。循环共创建13 面竖直隔板i从 0 到 12宽度 10px、高度 100px中心 y height - 50即占据画布最底部 100pxy 从 600 到 700。按spacing等距排布后第一面隔板位于 x 0画布左边缘第 12 面恰好位于 x 600画布右边缘最后一面 x ≈ 654.5 落在画布之外相邻隔板之间形成一个宽约 44.5px54.5 − 10的格口正好让直径 20px 的粒子落位并堆叠画布可见区域内共 11 个格口即文档所说的 “buckets”——粒子穿过图钉阵后会分别落入底部这些分格中。隔板与地面在 y 700 处恰好相接形成封闭的落位槽。4.3 图钉阵与篮筐的配合图钉阵列Part 1 建立的布局参数为rows 10、每行cols 1 12个图钉偶数行整体右移spacing / 2形成交错排布sketch.js 第 52-63 行。钉阵最底一行位于 y ≈ 545而篮筐区从 y 600 开始两者之间留有约 55px 的“直落段”粒子在此段内基本不再发生碰撞落格分布主要由最后一排钉附近的反弹决定。4.4 墙体的绘制Boundary 的 show 方法 用白色填充和描边、rectMode(CENTER)按刚体中心绘制矩形使渲染几何与碰撞几何严格一致——这也是 Matter.js 示例中推荐的做法始终以this.body.position为渲染基准而非自己另存一份坐标。5. 刚体生命周期周期性生成与离屏删除Part 2 文档的第二项任务 “delete offscreen bodies” 由三处代码协作完成。5.1 生成function newParticle() { var p new Particle(300, 0, 10); particles.push(p); } function draw() { background(0, 0, 0); if (frameCount % 20 0) { newParticle(); // 每 20 帧生成一个粒子 } Engine.update(engine, 1000 / 30); // 固定时间步长 ... }Engine.update(engine, 1000 / 30)以约 33.3ms 的固定时间步长推进物理世界使模拟速度与渲染帧率解耦粒子则以固定频率每 20 帧从画布顶部中心生成形成持续不断的粒子流。5.2 离屏判定Particle.isOffScreen 定义了“离开世界”的条件Particle.prototype.isOffScreen function() { var x this.body.position.x; var y this.body.position.y; return x -50 || x width 50 || y height; };横向各留 50px 余量大于粒子半径 10允许粒子略微越界而不误删纵向则是一旦y height低于画布底边即判定离场。正常情况下粒子会停驻在篮筐内不会触发y height该分支主要覆盖从最外侧格口翻出、越过画布边缘等异常路径。5.3 从世界中移除draw 循环中的清理逻辑for (var i 0; i particles.length; i) { particles[i].show(); if (particles[i].isOffScreen()) { World.remove(world, particles[i].body); // 从物理世界移除停止求解 particles.splice(i, 1); // 从 JS 数组移除 i--; // 回退下标避免跳过元素 } }这里有两个要点World.remove必不可少仅从particles数组中splice只会丢失 JS 引用刚体仍留在 Matter.js 世界中继续参与碰撞与求解会造成“幽灵刚体”和内存增长必须显式调用World.remove(world, body)将其从世界中摘除正向遍历 i--splice(i, 1)会使后续元素前移若不将下标回退一格被删元素的“后继”将跳过本帧的show()与离屏检查。由于粒子流是持续的每 20 帧新增一个若无此清理机制世界中的刚体数量将随运行时间无界增长物理求解开销也随之线性上升。离屏删除是这类“无限生成型”模拟的必备生命周期管理手段。6. 碰撞事件与 label 匹配机制sketch.js 第 35-49 行 在setup()中注册了全局碰撞监听function collision(event) { var pairs event.pairs; for (var i 0; i pairs.length; i) { var labelA pairs[i].bodyA.label; var labelB pairs[i].bodyB.label; if (labelA particle labelB plinko) { //ding.play(); } if (labelA plinko labelB particle) { //ding.play(); } } } Events.on(engine, collisionStart, collision);Matter.js 的collisionStart事件回调会携带event.pairs每对碰撞体以bodyA/bodyB给出但先后顺序不定所以代码对两种排列都做了判断。这正是第 3 节中为Particle与Plinko设置label的用途label 是 Matter.js 官方推荐的碰撞对象区分手段配合collisionFilter可实现更细粒度的过滤。当前源码中ding.play()处于注释状态但preload()已经通过ding loadSound(ding.mp3)完成了音频加载注意此处未加varding成为全局变量。这与文档脉络一致音效属于 Part 4 的内容而事件骨架与资源加载在当前源码中已就位。7. 从源码结构看系列演进与可动手的扩展点对照各部分文档的描述可以推断当前 CC_062_plinko/P5 中的源码是四部分系列的累积结果图钉阵与粒子流Part 1、Boundary类与离屏清理Part 2、粒子初始 x 的random(-1, 1)抖动Part 3“加入随机性”的最简形式、以及已加载但暂未启用的铃声Part 4。文档中world.gravity.y 2一行被注释掉sketch.js 第 33 行说明重力保持 Matter.js 默认值。在本文档所述范围内示例提供了几个直接的参数化扩展入口全部可在源码中找到对应位置修改cols/rowssketch.js 第 21-22 行改变钉阵规模与篮筐格数注意spacing width / cols会连带改变隔板位置调整粒子与图钉的restitution/friction改变弹跳节奏与滞留时间取消 碰撞回调 中ding.play()的注释在用户触发浏览器声音策略后即可获得撞钉音效调整 isOffScreen 的 50px 阈值 或生成周期frameCount % 20控制清理灵敏度与粒子流密度。8. 小结Part 2 在 Part 1 的粒子模拟之上完成了三件相互独立又彼此配套的事用Boundary静态矩形搭建地面与 13 面分格隔板形成 11 个可见篮筐格口用frameCount % 20维持稳定粒子流、以固定时间步长Engine.update(engine, 1000 / 30)驱动模拟通过isOffScreen判定加World.remove加数组splice的三段式清理保证刚体集合不会无限膨胀。配合基于label的collisionStart监听这套代码构成了一个可长期运行、资源可控的 Plinko 物理场景也为此后的随机性调参与音效增强Part 3、Part 4预留了干净的接入点。完整示例代码见 CodingChallenges/CC_062_plinko/P5/sketch.js、particle.js、plinko.js、boundary.js 与 index.html。赞分享教程文档示例工程【免费下载链接】website-archiveArchive of the Coding Train website (first version)项目地址https://gitcode.com/gh_mirrors/we/website-archive点击查看免费下载相关推荐Coding Train 编码挑战 62.3Matter.js Plinko 模拟的粒子随机化与物理调参实战Coding Train 编码挑战 62.3Matter.js Plinko 模拟的粒子随机化与物理调参实战 本篇技术指南围绕 Coding Train 网站教程文档示例工程用 Matter.js 与 p5.js 打造 Plinko 弹珠机模拟Coding Challenge 62.1 源码实战解析用 Matter.js 与 p5.js 打造 Plinko 弹珠机模拟Coding Challenge 62.1 源码实战解析 本篇技术指南以 Coding教程文档示例工程Konado物理模拟为对话场景添加真实感Konado物理模拟为对话场景添加真实感 引言从静态对话到动态沉浸 在传统视觉小说中对话场景往往是静态的——角色立绘固定位置背景静止不变整个画面缺乏生游戏开发开发工具上一篇解决Refined-GitHub插件中隐藏非活跃部署功能失效的完整指南下一篇ZyPlayer你的私人影视管家一个应用搞定全网视频资源创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考