使用 MatterJS 防止通过其他物体强制拖动物体

d13*_*d13 17 javascript physics matter.js

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

在此处输入图片说明

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

不幸的是,这会根据拖放操作破坏任何游戏或模拟。我尝试了许多解决方案,例如在发生碰撞时打破鼠标约束,或降低约束刚度,但没有任何可靠的方法。

欢迎任何建议!

Wil*_*ler 10

我认为这里最好的答案是对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);
});
Run Code Online (Sandbox Code Playgroud)
<canvas id="world"></canvas>
<script src="https://cdnjs.cloudflare.com/ajax/libs/matter-js/0.10.0/matter.js"></script>
Run Code Online (Sandbox Code Playgroud)

在示例中,我将velocitypositionImpulseinx和限制y25.0. 结果如下图所示

在此处输入图片说明

如您所见,拖动尸体可能会非常猛烈,并且它们不会相互穿过。这就是这种方法与其他方法的不同之处:大多数其他潜在的解决方案在用户拖拽时变得足够猛烈时失败。

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


解决方案2

这是一个额外的解决方案,但公平警告:它并不简单。

从广义上讲,它的工作方式是检查被拖动的物体是否dragBody与静态物体发生碰撞,以及鼠标是否在没有dragBody跟随的情况下移动得太远。如果它检测到鼠标和鼠标之间的距离dragBody变得太大,它会从中删除事件侦听器并用不同的 mousemove 函数替换它。此函数检查鼠标是否已返回到身体中心的给定范围内。不幸的是,我无法让内置方法正常工作,所以我不得不直接包含它(在 Javascript 方面比我更了解的人必须弄清楚这一点)。最后,如果检测到事件,它将切换回普通侦听器。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);
});
Run Code Online (Sandbox Code Playgroud)
<canvas id="world"></canvas>
<script src="https://cdnjs.cloudflare.com/ajax/libs/matter-js/0.10.0/matter.js"></script>
Run Code Online (Sandbox Code Playgroud)

应用事件侦听器切换方案后,主体现在的行为更像这样

在此处输入图片说明

我已经对此进行了相当彻底的测试,但我不能保证它在所有情况下都能正常工作。还值得注意的是,mouseup除非鼠标在发生时位于画布内,否则不会检测到该事件 - 但对于任何 Matter.jsmouseup检测都是如此,所以我没有尝试解决这个问题。

如果速度足够大,Resolver将无法检测到任何碰撞,并且由于它缺乏对这种物理冲突的预测性预防,将允许身体通过,如图所示。

在此处输入图片说明

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

最后要注意的是,可以仅将其应用于某些交互(例如静态和非静态主体之间的交互)。这样做是通过改变

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

到(例如静态物体)

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

失败的解决方案

在任何情况下,未来的用户遇到这个问题,并发现不足,它们的使用情况下这两种解决方案,这里有一些我所尝试的解决方案,做到了工作。各种不该做什么的指南。

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


小智 0

这似乎与 GitHub 页面上的问题 672有关,该页面似乎表明这是由于缺乏连续碰撞检测 (CCD) 造成的。

已经尝试解决此问题,并且可以在此处找到其代码,但问题仍然悬而未决,因此看起来您可能需要编辑引擎以自己构建 CCD。