使用MatterJS防止通过其他物体拖拉物体


14

我正在将MatterJs用于基于物理的游戏,但尚未找到解决方案来防止鼠标通过其他物体强行拖动物体。如果将一个主体拖到另一个主体中,则被拖动的主体可能会迫使自己进入另一个主体并通过另一个主体。我正在寻找一种可靠的方法来防止它们相交。您可以在任何MatterJS演示中观察这种效果,方法是用鼠标选择一个主体,然后尝试迫使它穿过另一个主体。这是一个典型的例子:

在此处输入图片说明

https://brm.io/matter-js/demo/#staticFriction

不幸的是,这会根据拖放而中断任何游戏或模拟。我尝试了许多解决方案,例如在发生碰撞时打破鼠标约束,或减小约束刚度,但没有一种可靠地工作。

任何建议欢迎!


我不明白用力拖拉的措辞。您是说被拖拽的身体应该经过其他任何身体吗?
grodzi '19

不,这意味着应防止被拖动的物体穿过任何其他物体。
d13

1
@ d13您可以添加显示问题的动画吗?由于措辞似乎有些混乱……
Ghost

2
@Ghost添加了...
d13

@ d13这使事情更清楚.....这是一个棘手的问题
Ghost

Answers:


6

我认为,最好的答案是对Matter.Resolver模块进行重大改革,以实现对任何机构之间的物理冲突的预测性避免。缺少这些保证在某些情况下会失败。话虽这么说,但这是两个“解决方案”,实际上只是部分解决方案。它们概述如下。


解决方案1(更新)

该解决方案具有以下优点:

  • 解决方案2更简洁
  • 它比解决方案2产生的计算占用空间较小
  • 拖动行为不会像在解决方案2中那样被打断
  • 可以与解决方案2无损结合

这种方法背后的思想是通过使力可停止来解决“ 当不可阻挡的力遇到不可移动的物体时 ” 发生的悖论。可以通过启用此功能Matter.Event beforeUpdate,该功能可以将positionImpulse每个方向上的绝对速度和脉冲(或者实际上不是物理脉冲)限制在用户定义的范围内。

window.addEventListener('load', function() {
    var canvas = document.getElementById('world')
    var mouseNull = document.getElementById('mouseNull')
    var engine = Matter.Engine.create();
    var world = engine.world;
    var render = Matter.Render.create({    element: document.body, canvas: canvas,
                 engine: engine, options: { width: 800, height: 800,
                     background: 'transparent',showVelocity: true }});
    var body = Matter.Bodies.rectangle(400, 500, 200, 60, { isStatic: true}), 
        size = 50, counter = -1;
     
    var stack = Matter.Composites.stack(350, 470 - 6 * size, 1, 6, 
                                        0, 0, function(x, y) {
     return Matter.Bodies.rectangle(x, y, size * 2, size, {
         slop: 0, friction: 1,    frictionStatic: Infinity });
    });
    Matter.World.add(world, [ body, stack,
     Matter.Bodies.rectangle(400, 0, 800, 50, { isStatic: true }),
     Matter.Bodies.rectangle(400, 600, 800, 50, { isStatic: true }),
     Matter.Bodies.rectangle(800, 300, 50, 600, { isStatic: true }),
     Matter.Bodies.rectangle(0, 300, 50, 600, { isStatic: true })
    ]);

    Matter.Events.on(engine, 'beforeUpdate', function(event) {
     counter += 0.014;
     if (counter < 0) { return; }
     var px = 400 + 100 * Math.sin(counter);
     Matter.Body.setVelocity(body, { x: px - body.position.x, y: 0 });
     Matter.Body.setPosition(body, { x: px, y: body.position.y });
     if (dragBody != null) {
        if (dragBody.velocity.x > 25.0) {
            Matter.Body.setVelocity(dragBody, {x: 25, y: dragBody.velocity.y });
        }
        if (dragBody.velocity.y > 25.0) {
            Matter.Body.setVelocity(dragBody, {x: dragBody.velocity.x, y: 25 });
        }
        if (dragBody.positionImpulse.x > 25.0) {
            dragBody.positionImpulse.x = 25.0;
        }
        if (dragBody.positionImpulse.y > 25.0) {
            dragBody.positionImpulse.y = 25.0;
        }
    }
    });

    var mouse = Matter.Mouse.create(render.canvas),
     mouseConstraint = Matter.MouseConstraint.create(engine, { mouse: mouse,
         constraint: { stiffness: 0.1, render: { visible: false }}});
     
    var dragBody = null


    Matter.Events.on(mouseConstraint, 'startdrag', function(event) {
     dragBody = event.body;
    });
    
    Matter.World.add(world, mouseConstraint);
    render.mouse = mouse;
    Matter.Engine.run(engine);
    Matter.Render.run(render);
});
<canvas id="world"></canvas>
<script src="https://cdnjs.cloudflare.com/ajax/libs/matter-js/0.10.0/matter.js"></script>

在示例中,我将velocity和限制positionImpulsexy的最大值25.0。结果如下所示

在此处输入图片说明

如您所见,拖动身体可能会非常猛烈,它们不会相互穿过。这就是该方法与众不同的地方:当用户对其拖动产生足够暴力时,大多数其他潜在解决方案将失败。

我用这种方法遇到的唯一缺点是,可以使用一个非静态物体撞击另一个非静态物体,使其足够坚硬,以使其具有足够的速度,使Resolver模块无法检测到碰撞并允许碰撞。第二身体通过其他身体。(在静摩擦示例中,所需的速度大约是50.0,我只成功完成了一次,因此没有动画来描绘它)。


解决方案2

这是一个附加的解决方案,尽管警告也很合理:这并不简单。

从广义上讲,此方法的工作方式是检查被拖动的主体是否dragBody与静态主体发生碰撞,以及此后鼠标是否移动得太远而没有dragBody跟随。如果它检测到鼠标之间的距离dragBody已经过大,则会从中删除事件侦听器,并用其他的mousemove函数替换它。此功能检查鼠标是否已返回到身体中心的给定距离内。不幸的是,我无法使内置方法正常工作,因此我不得不直接将其包括在内(比我更精通Java的人必须弄清楚这一点)。最后,如果检测到事件,它将切换回普通侦听器。Matter.js mouse.mousemovemouse.elementmousemove()Matter.Mouse._getRelativeMousePosition()mouseupmousemove

window.addEventListener('load', function() {
    var canvas = document.getElementById('world')
    var mouseNull = document.getElementById('mouseNull')
    var engine = Matter.Engine.create();
    var world = engine.world;
    var render = Matter.Render.create({ element: document.body, canvas: canvas,
                 engine: engine, options: { width: 800, height: 800,
                     background: 'transparent',showVelocity: true }});
    var body = Matter.Bodies.rectangle(400, 500, 200, 60, { isStatic: true}), 
        size = 50, counter = -1;
     
    var stack = Matter.Composites.stack(350, 470 - 6 * size, 1, 6, 
                                        0, 0, function(x, y) {
     return Matter.Bodies.rectangle(x, y, size * 2, size, {
         slop: 0.5, friction: 1,    frictionStatic: Infinity });
    });
    Matter.World.add(world, [ body, stack,
     Matter.Bodies.rectangle(400, 0, 800, 50, { isStatic: true }),
     Matter.Bodies.rectangle(400, 600, 800, 50, { isStatic: true }),
     Matter.Bodies.rectangle(800, 300, 50, 600, { isStatic: true }),
     Matter.Bodies.rectangle(0, 300, 50, 600, { isStatic: true })
    ]);

    Matter.Events.on(engine, 'beforeUpdate', function(event) {
     counter += 0.014;
     if (counter < 0) { return; }
     var px = 400 + 100 * Math.sin(counter);
     Matter.Body.setVelocity(body, { x: px - body.position.x, y: 0 });
     Matter.Body.setPosition(body, { x: px, y: body.position.y });
    });

    var mouse = Matter.Mouse.create(render.canvas),
     mouseConstraint = Matter.MouseConstraint.create(engine, { mouse: mouse,
         constraint: { stiffness: 0.2, render: { visible: false }}});
     
    var dragBody, overshoot = 0.0, threshold = 50.0, loc, dloc, offset, 
    bodies = Matter.Composite.allBodies(world), moveOn = true;
    getMousePosition = function(event) {
     var element = mouse.element, pixelRatio = mouse.pixelRatio, 
        elementBounds = element.getBoundingClientRect(),
        rootNode = (document.documentElement || document.body.parentNode || 
                    document.body),
        scrollX = (window.pageXOffset !== undefined) ? window.pageXOffset : 
                   rootNode.scrollLeft,
        scrollY = (window.pageYOffset !== undefined) ? window.pageYOffset : 
                   rootNode.scrollTop,
        touches = event.changedTouches, x, y;
     if (touches) {
         x = touches[0].pageX - elementBounds.left - scrollX;
         y = touches[0].pageY - elementBounds.top - scrollY;
     } else {
         x = event.pageX - elementBounds.left - scrollX;
         y = event.pageY - elementBounds.top - scrollY;
     }
     return { 
         x: x / (element.clientWidth / (element.width || element.clientWidth) *
            pixelRatio) * mouse.scale.x + mouse.offset.x,
         y: y / (element.clientHeight / (element.height || element.clientHeight) *
            pixelRatio) * mouse.scale.y + mouse.offset.y
     };
    };     
    mousemove = function() {
     loc = getMousePosition(event);
     dloc = dragBody.position;
     overshoot = ((loc.x - dloc.x)**2 + (loc.y - dloc.y)**2)**0.5 - offset;
     if (overshoot < threshold) {
         mouse.element.removeEventListener("mousemove", mousemove);
         mouse.element.addEventListener("mousemove", mouse.mousemove);
         moveOn = true;
     }
    }
    Matter.Events.on(mouseConstraint, 'startdrag', function(event) {
     dragBody = event.body;
     loc = mouse.position;
     dloc = dragBody.position;
     offset = ((loc.x - dloc.x)**2 + (loc.y - dloc.y)**2)**0.5;
     Matter.Events.on(mouseConstraint, 'mousemove', function(event) {
         loc = mouse.position;
         dloc = dragBody.position;
         for (var i = 0; i < bodies.length; i++) {                      
             overshoot = ((loc.x - dloc.x)**2 + (loc.y - dloc.y)**2)**0.5 - offset;
             if (bodies[i] != dragBody && 
                 Matter.SAT.collides(bodies[i], dragBody).collided == true) {
                 if (overshoot > threshold) {
                     if (moveOn == true) {
                         mouse.element.removeEventListener("mousemove", mouse.mousemove);
                         mouse.element.addEventListener("mousemove", mousemove);
                         moveOn = false;
                     }
                 }
             }
         }
     });
    });

    Matter.Events.on(mouseConstraint, 'mouseup', function(event) {
     if (moveOn == false){
         mouse.element.removeEventListener("mousemove", mousemove);
         mouse.element.addEventListener("mousemove", mouse.mousemove);
         moveOn = true;
     }
    });
    Matter.Events.on(mouseConstraint, 'enddrag', function(event) {
     overshoot = 0.0;
     Matter.Events.off(mouseConstraint, 'mousemove');
    });

    Matter.World.add(world, mouseConstraint);
    render.mouse = mouse;
    Matter.Engine.run(engine);
    Matter.Render.run(render);
});
<canvas id="world"></canvas>
<script src="https://cdnjs.cloudflare.com/ajax/libs/matter-js/0.10.0/matter.js"></script>

应用事件侦听器切换方案后,主体现在的行为类似于

在此处输入图片说明

我已经对此进行了相当全面的测试,但我不能保证它在每种情况下都能正常工作。还需要注意的是,mouseup除非事件发生时鼠标不在画布内,否则不会检测到该事件-但这对于任何Matter.js mouseup检测都是正确的,因此我没有尝试解决该问题。

如果速度足够大,Resolver将无法检测到任何碰撞,并且由于无法预测性地防止这种物理冲突的味道,因此将使身体通过,如下所示。

在此处输入图片说明

这可以通过与解决方案1结合解决

最后一点,可以将其仅应用于某些交互(例如,静态和非静态物体之间的交互)。这样做是通过改变来实现的

if (bodies[i] != dragBody && Matter.SAT.collides(bodies[i], dragBody).collided == true) {
    //...
}

到(例如静态物体)

if (bodies[i].isStatic == true && bodies[i] != dragBody && 
    Matter.SAT.collides(bodies[i], dragBody).collided == true) {
    //...
}

解决方案失败

如果将来有任何用户遇到此问题,并且发现这两种解决方案都不足以满足他们的用例,那么这是我尝试过的一些无效的解决方案。关于不做什么的各种指南。

  • mouse.mouseup直接调用:对象立即删除。
  • mouse.mouseup通过调用Event.trigger(mouseConstraint, 'mouseup', {mouse: mouse}):被覆盖Engine.update,行为不变。
  • 将拖动的对象暂时设为静态:对象返回非静态状态(无论通过Matter.Body.setStatic(body, false)还是body.isStatic = false)时都会删除。
  • 在接近冲突时将力设置为(0,0)通过setForce:对象仍可以通过,需要实施Resolver以实际工作。
  • mouse.element通过setElement()mouse.element直接更改为其他画布:对象立即删除。
  • 将对象还原到最后一个“有效”位置:仍然允许通过,
  • 通过collisionStart以下方式更改行为:不一致的碰撞检测仍然允许使用此方法通过


非常感谢您的贡献!我授予您赏金,因为即使您的解决方案并不完美,也肯定会指出前进的方向,并且您在此问题上投入了大量的思想和时间-谢谢!现在,我可以确定这个问题最终是MatterJS中的一个功能缺陷,希望这一讨论将为将来的真正解决方案做出贡献。
d13

@ d13谢谢,我同意问题最终出在底层代码中,但我很高兴能得到一些类似的解决方案
William Miller

0

我将以另一种方式管理该功能:

  • 没有“拖动”(因此,拖动点与偏移量与拖动的对象之间不会连续对齐)
  • 在mouseDown上,鼠标指针的位置给出对象要遵循的定向速度矢量
  • 在mouseUp上重置速度矢量
  • 让物质模拟完成其余的工作

1
这还不是如何matter.js处理拖曳物体的方法吗?从这里开始 “……就像附着在鼠标上的虚拟弹簧。当拖动时……弹簧被附着到[主体]并沿鼠标的方向拉动……”
Ghost

仅设置速度可防止拖动重叠,而弹簧会迫使身体穿过其他物体。
Mosè地区Raguzzini

这实际上可能指向解决方案。如果我理解正确,那就意味着不使用MatterJS内置的MouseConstraint,而是根据鼠标的位置手动设置身体的速度。但是我不确定该如何实现,因此,如果任何人都可以发布有关如何将主体与鼠标位置对齐的详细信息,而无需使用setPosition或约束,请这样做。
d13

@ d13您仍将依靠MatterJS Resolver来决定如何处理碰撞体-在仔细检查了该代码后,我希望它在许多情况下仍会决定允许通过....还实施了自己的版本solveVelocitysolvePosition,但在这一点上你仍然手动做你想要MatterJS直接处理什么....

0

要在拖动时控制碰撞,您需要利用碰撞过滤器事件

创建具有默认碰撞滤镜蒙版的实体 0x0001。添加捕获startdragenddrag事件,并设置不同的身体碰撞过滤器类别以暂时避免碰撞。

Matter.Events.on(mouseConstraint, 'startdrag', function(event) {
    event.body.collisionFilter.category = 0x0008; // move body to new category to avoid collision
});
Matter.Events.on(mouseConstraint, 'enddrag', function(event) {
     event.body.collisionFilter.category = 0x0001; // return body to default category to activate collision
});

window.addEventListener('load', function () {

  //Fetch our canvas
  var canvas = document.getElementById('world');

  //Setup Matter JS
  var engine = Matter.Engine.create();
  var world = engine.world;
  var render = Matter.Render.create({
                                      canvas: canvas,
                                      engine: engine,
                                      options: {
                                        width: 800,
                                        height: 800,
                                        background: 'transparent',
                                        wireframes: false,
                                        showAngleIndicator: false
                                      }
                                    });

  //Add a ball
  const size = 50;
  const stack = Matter.Composites.stack(350, 470 - 6 * size, 1, 6, 0, 0, (x, y) => {
    return Matter.Bodies.rectangle(x, y, size * 2, size, {
      collisionFilter: {
            mask: 0x0001,
      },
      slop: 0.5,
      friction: 1,
      frictionStatic: Infinity,
    });
  });

  Matter.World.add(engine.world, stack);

  //Add a floor
  var floor = Matter.Bodies.rectangle(250, 520, 500, 40, {
    isStatic: true, //An immovable object
    render: {
      visible: false
    }
  });
  Matter.World.add(world, floor);

  //Make interactive
  var mouseConstraint = Matter.MouseConstraint.create(engine, { //Create Constraint
    element: canvas,

    constraint: {
      render: {
        visible: false
      },
      stiffness: 0.8
    }
  });
  Matter.World.add(world, mouseConstraint);

  // add events to listen drag
  Matter.Events.on(mouseConstraint, 'startdrag', function (event) {
    event.body.collisionFilter.category = 0x0008; // move body to new category to avoid collision
  });
  Matter.Events.on(mouseConstraint, 'enddrag', function (event) {
    event.body.collisionFilter.category = 0x0001; // return body to default category to activate collision
  });

  //Start the engine
  Matter.Engine.run(engine);
  Matter.Render.run(render);

});
<canvas id="world"></canvas>
<script src="https://cdnjs.cloudflare.com/ajax/libs/matter-js/0.10.0/matter.min.js"></script>


1
非常感谢您的出色演示!我实际上是在试图达到相反的效果:当一个人拖到另一个时,我需要防止身体相交。
d13

抱歉,如果我误解了这个问题。您能通过阻止身体相交来阐明您的意思吗?您是否要防止在施加力时拖动其他物体?
Temur Tchanukvadze

1
在这种情况下,这是一个未解决的问题,如果没有硬编码来实现CCD,就不可能做到。看一下:github.com/liabru/matter-js/issues/5
Temur Tchanukvadze

0

这似乎与他们的GitHub页面上的问题672有关,这似乎表明这是由于缺少连续碰撞检测(CCD)导致的。

已经进行了补救,并且可以在此处找到其代码,但是问题仍然存在,因此您似乎可能需要编辑引擎才能自行将CCD内置到其中。


1
感谢您的回答!我已经考虑过了,但是我认为这不是CCD问题,而是“无法阻挡的力量遇到不可移动的障碍会发生什么?”的问题。我需要以某种方式找出如何抵消力量以防止尸体相交的方法。
d13
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.