基于Matter.js实现Plinko游戏碰撞Ping波纹效果的技术问询
实现Plinko游戏中销的Ping波纹效果(基于Matter.js)
我正在重构Plinko游戏,想要实现Stake平台同款的Ping波纹效果——就是球碰撞销时,销周围会出现向外扩散的半透明波纹动画。目前已经通过Matter.js实现了球与销的碰撞事件触发,但Matter.js自带的渲染器功能有限,不知道怎么给销添加这个波纹效果。
效果示意图:
现有的碰撞检测代码片段:
// if (bodyB.label.includes("ball") && bodyA.label.includes("pin")) { // console.log("Ball hit pin"); // // Style pin to create a wave effect when hit // bodyA.render.fillStyle = "red"; // }
可行解决方案
方案一:叠加独立Canvas绘制波纹
Matter.js的渲染基于Canvas,你可以在游戏画布上层叠加一个独立的Canvas(绝对定位,和游戏画布重合),专门用来绘制波纹动画:
- 碰撞触发时,记录被碰撞销的坐标,生成包含半径、透明度、扩散速度的波纹实例
- 在游戏的动画循环中,遍历波纹数组更新参数并绘制,过期的波纹直接移除
示例代码:
// 初始化波纹数组与叠加Canvas const ripples = []; const rippleCanvas = document.createElement('canvas'); rippleCanvas.width = window.innerWidth; rippleCanvas.height = window.innerHeight; rippleCanvas.style.position = 'absolute'; rippleCanvas.style.top = '0'; rippleCanvas.style.left = '0'; rippleCanvas.style.pointerEvents = 'none'; // 不干扰游戏交互 document.body.appendChild(rippleCanvas); const rippleCtx = rippleCanvas.getContext('2d'); // 碰撞事件中添加波纹 Matter.Events.on(engine, 'collisionStart', function(event) { const pairs = event.pairs; pairs.forEach(pair => { const { bodyA, bodyB } = pair; const pinBody = bodyA.label.includes('pin') ? bodyA : bodyB; if ((bodyA.label.includes('pin') && bodyB.label.includes('ball')) || (bodyA.label.includes('ball') && bodyB.label.includes('pin'))) { ripples.push({ x: pinBody.position.x, y: pinBody.position.y, radius: 0, opacity: 1, maxRadius: 30, speed: 2, fadeSpeed: 0.02 }); } }); }); // 动画循环中绘制波纹 Matter.Events.on(engine, 'afterUpdate', function() { rippleCtx.clearRect(0, 0, rippleCanvas.width, rippleCanvas.height); ripples.forEach((ripple, index) => { ripple.radius += ripple.speed; ripple.opacity -= ripple.fadeSpeed; if (ripple.opacity <= 0 || ripple.radius >= ripple.maxRadius) { ripples.splice(index, 1); return; } rippleCtx.beginPath(); rippleCtx.arc(ripple.x, ripple.y, ripple.radius, 0, Math.PI * 2); rippleCtx.strokeStyle = `rgba(255, 255, 255, ${ripple.opacity})`; // 可自定义波纹颜色 rippleCtx.lineWidth = 2; rippleCtx.stroke(); }); });
方案二:用CSS动画配合DOM元素
如果允许动态生成DOM元素,可以用CSS的缩放和透明度动画实现波纹:
- 预定义波纹的CSS动画样式
- 碰撞时在销的坐标位置生成DOM元素,触发动画后自动移除
示例代码:
.pin-ripple { position: absolute; border-radius: 50%; border: 2px solid white; pointer-events: none; animation: ripple 0.6s ease-out forwards; } @keyframes ripple { from { transform: scale(0); opacity: 1; } to { transform: scale(2); opacity: 0; } }
Matter.Events.on(engine, 'collisionStart', function(event) { const pairs = event.pairs; pairs.forEach(pair => { const { bodyA, bodyB } = pair; let pinBody; if (bodyA.label.includes('pin') && bodyB.label.includes('ball')) { pinBody = bodyA; } else if (bodyA.label.includes('ball') && bodyB.label.includes('pin')) { pinBody = bodyB; } if (pinBody) { // 转换Matter坐标到页面坐标(根据你的画布定位调整) const canvas = document.getElementById('your-game-canvas'); const rect = canvas.getBoundingClientRect(); const x = pinBody.position.x + rect.left; const y = pinBody.position.y + rect.top; const ripple = document.createElement('div'); ripple.className = 'pin-ripple'; ripple.style.width = '20px'; ripple.style.height = '20px'; ripple.style.left = `${x - 10}px`; // 让波纹居中于销 ripple.style.top = `${y - 10}px`; document.body.appendChild(ripple); // 动画结束后自动移除元素 ripple.addEventListener('animationend', () => { ripple.remove(); }); } }); });
注意事项
- Canvas方案要确保两个画布的坐标系统一致,避免波纹偏移
- CSS方案要注意游戏画布是否有缩放/变换,需正确转换坐标
- 两种方案都要控制波纹数量,避免过多波纹导致性能问题
内容的提问来源于stack exchange,提问作者Ibra
相关产品推荐
相关产品推荐

