基于matter.js的Plinko游戏:如何让球看似随机落入底部第三格
实现Plinko小球定向落位(看似随机但必入底部第三个空位)
问题描述
基于给定的Plinko游戏代码,需要实现小球下落过程看似随机,但最终必然落入底部第三个空位。核心是平衡自然的随机运动轨迹与确定的落位结果,不能让引导痕迹过于明显。
实现思路
- 利用Matter.js的碰撞事件监听,在小球每次碰撞障碍物后触发调整逻辑
- 计算小球当前位置与目标空位的水平偏移,施加与偏移成正比的定向力,引导小球向目标方向移动
- 加入随机扰动(小幅度随机调整力的方向和大小),让运动轨迹看起来自然无规律
- 限制最大作用力,避免小球突然变向,保证运动的连贯性和真实感
修改后的完整代码
JavaScript(script.js)
const Engine = Matter.Engine, Render = Matter.Render, World = Matter.World, Bodies = Matter.Bodies, Body = Matter.Body, Events = Matter.Events; // 新增事件模块 // create an engine const engine = Engine.create(); // create a renderer const render = Render.create({ element: document.getElementById('can'), engine: engine }); // create flanges const fOptions = { isStatic: true, } const flanges = [ //row one Bodies.polygon(45, 150, 3, 20, fOptions), Bodies.polygon(100, 250, 3, 20, fOptions), Bodies.polygon(60, 350, 3, 20, fOptions), Bodies.polygon(100, 450, 3, 20, fOptions), //row two Bodies.polygon(150, 150, 3, 20, fOptions), Bodies.polygon(200, 250, 3, 20, fOptions), Bodies.polygon(150, 350, 3, 20, fOptions), Bodies.polygon(200, 450, 3, 20, fOptions), //row three Bodies.polygon(250, 150, 3, 20, fOptions), Bodies.polygon(300, 250, 3, 20, fOptions), Bodies.polygon(250, 350, 3, 20, fOptions), Bodies.polygon(300, 450, 3, 20, fOptions), //row four Bodies.polygon(350, 150, 3, 20, fOptions), Bodies.polygon(400, 250, 3, 20, fOptions), Bodies.polygon(350, 350, 3, 20, fOptions), Bodies.polygon(400, 450, 3, 20, fOptions), //row five Bodies.polygon(450, 150, 3, 20, fOptions), Bodies.polygon(500, 250, 3, 20, fOptions), Bodies.polygon(450, 350, 3, 20, fOptions), Bodies.polygon(500, 450, 3, 20, fOptions), //row six Bodies.polygon(550, 150, 3, 20, fOptions), Bodies.polygon(600, 250, 3, 20, fOptions), Bodies.polygon(550, 350, 3, 20, fOptions), Bodies.polygon(600, 450, 3, 20, fOptions), //row seven Bodies.polygon(650, 150, 3, 20, fOptions), Bodies.polygon(700, 250, 3, 20, fOptions), Bodies.polygon(650, 350, 3, 20, fOptions), Bodies.polygon(700, 450, 3, 20, fOptions), //row 8 Bodies.polygon(755, 150, 3, 20, fOptions), Bodies.polygon(740, 350, 3, 20, fOptions), ]; for (i = 0; i < flanges.length; i++) { World.add(engine.world, [flanges[i]]) Body.setAngle(flanges[i], 16.23) } //create catches const cOptions = { isStatic: true } const catches = [ Bodies.rectangle(40, 560, 2, 40, cOptions) ]; xasx = 82; for (v = 0; v < 20; v++) { catches.push(Bodies.rectangle(xasx, 560, 2, 40, cOptions)) World.add(engine.world, [catches[v]]) xasx = xasx + 45; } // create disk let disk = []; let count = 0; const dOptions = { friction: .2, restitution: 1 } const TARGET_X = 127; // 底部第三个空位的x坐标 function addD() { const newDisk = Bodies.circle(event.offsetX, event.offsetY, 20, dOptions) disk.push(newDisk); World.add(engine.world, [newDisk]); // 监听碰撞事件,引导小球向目标位置移动 Events.on(newDisk, 'collisionStart', function(event) { const ball = event.bodyA === newDisk ? event.bodyA : event.bodyB; const diffX = TARGET_X - ball.position.x; // 施加定向力+随机扰动,保证运动自然 const forceX = diffX * 0.1 + (Math.random() - 0.5) * 0.8; const forceY = (Math.random() - 0.5) * 0.2; // 限制最大力,避免突兀变向 const maxForce = 2; Body.applyForce(ball, ball.position, { x: Math.max(-maxForce, Math.min(maxForce, forceX)), y: Math.max(-maxForce, Math.min(maxForce, forceY)) }); }); count++; } // create ground sky and walls const ground = Bodies.rectangle(400, 610, 810, 60, { isStatic: true }); const leftWall = Bodies.rectangle(0, 305, 2, 810, { isStatic: true }); const righttWall = Bodies.rectangle(800, 305, 2, 810, { isStatic: true }); const sky = Bodies.rectangle(400, 0, 810, 2, { isStatic: true }); // add all of the bodies to the world World.add(engine.world, [sky, righttWall, leftWall, ground]); // run the engine Engine.run(engine); // run the renderer Render.run(render);
CSS(styles.css)
* { margin: 0; touch-action: manipulation; } body { background-color: #353535; } h1 { font-family: 'Luckiest Guy', serif; color: white; background: -webkit-linear-gradient(pink, blueviolet); -webkit-background-clip: text; -webkit-text-fill-color: transparent; font-size: 100px; margin-bottom: 1%; } hr { width: 70%; margin-bottom: 2%; border-radius: 90%; background-color: pink; color: pink; } h4 { margin-top: 3%; color: darkorange; } a { color: darkorange; text-decoration: none; } a:hover { text-decoration: underline; }
HTML(index.html)
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <title>Plinko</title> <link href="styles.css" rel="stylesheet" /> <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Luckiest+Guy" /> </head> <body> <center> <h1>PlinkO</h1> <hr /> <aside onclick="addD()" id="can"></aside> </center> </body> <script src="https://cdn.bootcdn.net/ajax/libs/matter-js/0.19.0/matter.js"></script> <script src="script.js"></script> </html>
关键说明
- 定向引导逻辑:在每次碰撞后,根据小球与目标空位的水平距离施加拉力,距离越远拉力越大,确保小球逐步向目标靠近
- 随机扰动:在定向力基础上加入小范围随机值,让每次下落的轨迹都有差异,模拟真实Plinko的随机感
- 力的限制:通过
maxForce控制最大作用力,避免小球出现不符合物理规律的突然变向,保证运动自然流畅
内容的提问来源于stack exchange,提问作者add dd
相关产品推荐
相关产品推荐

