Matter.js链式吸附问题:末端无法贴合目标物体
问题与解决方案
需求
拖动链条末端至右侧物体时,末端与该物体保持平行贴合;尝试添加约束无效,希望实现凹槽嵌入/脱离效果,或直接实现贴合效果。
问题分析
原代码中约束失效的核心原因:
- 约束的
pointB坐标错误,未对应socket的正确贴合位置 - 解除约束时变量引用错误(
endConstraint未定义) - 距离判断阈值不合理,导致吸附/解除逻辑触发时机偏差
实现方案
1. 修正约束实现平行贴合
要让链条末端与socket平行,需给链条末端的两个点分别绑定到socket的对应平行点,而非同一个点:
- 链条末端的顶部点
{x:25, y:5}对应socket的左侧顶部点{x:-30, y:5} - 链条末端的底部点
{x:25, y:-5}对应socket的左侧底部点{x:-30, y:-5} - 调高约束
stiffness值(设为0.9),让贴合更牢固
2. 优化吸附/解除逻辑
- 缩小吸附触发的距离阈值(从46改为30),避免误触发
- 解除约束时先判断约束是否存在,避免报错
- 启用鼠标拖动结束时的检测逻辑,让拖拽链条末端时能正常解除吸附
3. 凹槽嵌入效果实现
调整socket的形状,将notch(凹槽)设置为与链条末端尺寸匹配的缺口,同时设置碰撞过滤让链条末端能嵌入:
- 把
notch的位置调整到socket左侧,尺寸设为20,10(与链条末端高度一致) - 给
notch设置collisionFilter: {mask: 0},让它不与链条发生碰撞,仅作为视觉凹槽
修正后的完整代码
JavaScript代码
window.addEventListener("load", function () { const { Engine, Render, World, Bodies, Composite, Runner, Mouse, MouseConstraint, Constraint, Events,Body,Composites } = Matter; var engine = Engine.create(); var render = Render.create({ element: document.body, engine }); // 创建带凹槽的插座 const mainRectangle = Bodies.rectangle(600, 200, 40, 20, { isStatic: true, render: { fillStyle: "#ff0000" } }); // 凹槽:尺寸与链条末端匹配,仅视觉效果,不参与碰撞 const notch = Bodies.rectangle(580, 200, 20, 10,{ isStatic: true, collisionFilter: {mask: 0}, // 不与任何物体碰撞 render: {fillStyle: "#ffffff"} }); const socket = Body.create({ parts: [mainRectangle, notch], isStatic: true }); World.add(engine.world, socket); // 创建链 var group = Body.nextGroup(true); var ropeA = Composites.stack(100, 50, 10, 1, 5, 5, function (x, y) { return Bodies.rectangle(x, y, 50, 10, { collisionFilter: { group: group }, damping:1, frictionAir: 0.001 }); }); Composites.chain(ropeA, 0.5, 0, -0.5, 0, { stiffness: 0.8, length: 2, render: { type: "line" } }); Composite.add( ropeA, Constraint.create({ bodyB: ropeA.bodies[0], pointB: { x: -25, y: 0 }, pointA: { x: ropeA.bodies[0].position.x, y: ropeA.bodies[0].position.y }, damping:1, stiffness: 0.8 }) ); Composite.add(engine.world, [ ropeA, Bodies.rectangle(400, 600, 1200, 50.5, { isStatic: true }), ]); // 添加鼠标控制 var mouse = Mouse.create(render.canvas); var mouseConstraint = MouseConstraint.create(engine, { mouse: mouse, constraint: { stiffness: 0.2, render: { visible: false } } }); Composite.add(engine.world, mouseConstraint); var runner = Runner.create(); Runner.run(runner, engine); Render.run(render); let isPluggedIn = false; let endConstraintTop = null, endConstraintBottom = null; // 帧更新时检测吸附 Events.on(engine, "beforeUpdate", function () { const endBody = ropeA.bodies[ropeA.bodies.length - 1]; const distance = Matter.Vector.magnitude( Matter.Vector.sub(socket.position, endBody.position) ); if (distance < 30 && !isPluggedIn) { // 创建平行约束,保证贴合 endConstraintTop = Constraint.create({ bodyA: endBody, pointA: { x: 25, y: 5 }, bodyB: socket, pointB: { x: -30, y: 5 }, stiffness: 0.9, length: 0 }); endConstraintBottom = Constraint.create({ bodyA: endBody, pointA: { x: 25, y: -5 }, bodyB: socket, pointB: { x: -30, y: -5 }, stiffness: 0.9, length: 0 }); Composite.add(engine.world, [endConstraintTop, endConstraintBottom]); isPluggedIn = true; } else if (isPluggedIn && distance > 35) { // 移除约束 if(endConstraintTop && endConstraintBottom){ Composite.remove(engine.world, [endConstraintTop, endConstraintBottom]); endConstraintTop = null; endConstraintBottom = null; } isPluggedIn = false; } }); // 鼠标拖动结束时检测是否解除吸附 Events.on(mouseConstraint, "enddrag", function (event) { if (event.body === ropeA.bodies[ropeA.bodies.length - 1] && isPluggedIn) { const endBody = ropeA.bodies[ropeA.bodies.length - 1]; const distance = Matter.Vector.magnitude( Matter.Vector.sub(socket.position, endBody.position) ); if (distance > 30) { if(endConstraintTop && endConstraintBottom){ Composite.remove(engine.world, [endConstraintTop, endConstraintBottom]); endConstraintTop = null; endConstraintBottom = null; } isPluggedIn = false; } } }); });
HTML代码
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <title>链条贴合效果演示</title> <script src="https://icdn.fongdan.com/js/matter.min.js"></script> </head> <body> </body> </html>
内容的提问来源于stack exchange,提问作者RorinL
相关产品推荐
相关产品推荐

