gpt4 book ai didi

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

转载 作者:行者123 更新时间:2023-12-04 01:10:00 30 4
gpt4 key购买 nike

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

enter image description here

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

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

任何建议欢迎!

最佳答案

我认为,最好的答案是对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>


    在示例中,我将 velocitypositionImpulse中的 xy限制为 25.0的最大大小。结果如下所示

    enter image description here

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

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

    解决方案2

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

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

    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>


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

    enter image description here

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

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

    enter image description here

    可以通过与 解决方案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更改为其他 Canvas :立即删除对象。
  • 将对象还原到最后一个“有效”位置:
  • 仍允许通过
  • 通过collisionStart更改行为:不一致的碰撞检测仍然允许使用此方法
  • 关于javascript - 使用MatterJS防止通过其他物体拖拉物体,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59321773/

    30 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com