Ngăn chặn các lực kéo qua các cơ thể khác bằng MatterJS


14

Tôi đang sử dụng MatterJs cho một trò chơi dựa trên vật lý và chưa tìm được giải pháp cho vấn đề ngăn cơ thể bị chuột kéo qua các cơ thể khác. Nếu bạn kéo một cơ thể vào một cơ thể khác, cơ thể bị kéo có thể tự ép mình vào và xuyên qua cơ thể kia. Tôi đang tìm kiếm một cách đáng tin cậy để ngăn chặn chúng giao nhau. Bạn có thể quan sát hiệu ứng này trong bất kỳ bản demo MatterJS nào bằng cách chọn một cơ thể bằng chuột và cố gắng ép nó qua một cơ thể khác. Đây là một ví dụ điển hình:

nhập mô tả hình ảnh ở đây

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

Thật không may, điều này phá vỡ bất kỳ trò chơi hoặc mô phỏng nào tùy thuộc vào kéo và thả. Tôi đã thử nhiều giải pháp, chẳng hạn như phá vỡ ràng buộc chuột khi xảy ra va chạm hoặc giảm độ cứng của ràng buộc, nhưng không có gì đáng tin cậy.

Mọi góp ý đều được chào đón!


Tôi không hiểu từ ngữ kéo theo lực lượng. Bạn có nghĩa là cơ thể bị kéo của bạn nên đi qua bất kỳ cơ thể khác?
Grodzi

Không, nó có nghĩa là cơ thể bị kéo nên được ngăn chặn đi qua bất kỳ cơ thể khác.
d13

1
@ d13 Bạn có thể thêm một hình ảnh động cho thấy vấn đề? Vì dường như có một số nhầm lẫn dựa trên từ ngữ ...
Ghost

2
@Ghost đã thêm ...
d13

@ d13 Điều đó làm cho mọi thứ rõ ràng hơn ..... đây là một điều khó khăn
Ghost

Câu trả lời:


6

Tôi nghĩ rằng câu trả lời tốt nhất ở đây sẽ là một cuộc đại tu đáng kể cho Matter.Resolvermô-đun để thực hiện dự đoán tránh xung đột vật lý giữa bất kỳ cơ quan nào. Bất cứ điều gì thiếu đó được đảm bảo để thất bại trong các trường hợp nhất định. Điều đó đang được nói ở đây là hai "giải pháp", trong thực tế, chỉ là một phần giải pháp. Chúng được phác thảo dưới đây.


Giải pháp 1 (Cập nhật)

Giải pháp này có một số ưu điểm:

  • Nó ngắn gọn hơn Giải pháp 2
  • Nó tạo ra một dấu chân tính toán nhỏ hơn Giải pháp 2
  • Hành vi kéo không bị gián đoạn như trong Giải pháp 2
  • Nó có thể không bị phá hủy kết hợp với Giải pháp 2

Ý tưởng đằng sau phương pháp này là giải quyết nghịch lý về những gì xảy ra " khi một lực không thể ngăn cản gặp một vật thể bất động " bằng cách khiến cho lực lượng có thể dừng lại được. Điều này được kích hoạt bởi Matter.Event beforeUpdate, cho phép vận tốc và xung tuyệt đối (hay đúng hơn positionImpulse, không thực sự là xung lực vật lý) theo mỗi hướng bị giới hạn trong giới hạn do người dùng xác định.

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>

Trong ví dụ này, tôi đang giới hạn velocitypositionImpulsetrong xvà ở ycường độ tối đa 25.0. Kết quả sẽ được hiển thị dưới đây

nhập mô tả hình ảnh ở đây

Như bạn có thể thấy, có thể khá dữ dội trong việc kéo các thi thể và chúng sẽ không truyền qua nhau. Đây là điều làm cho phương pháp này khác biệt với các phương pháp khác: hầu hết các giải pháp tiềm năng khác đều thất bại khi người dùng đủ bạo lực với sự lôi kéo của họ.

Thiếu sót duy nhất tôi gặp phải với phương pháp này là có thể sử dụng một vật thể không tĩnh để đâm vào một vật thể không tĩnh khác đủ mạnh để cung cấp cho nó đủ vận tốc đến điểm mà Resolvermô-đun sẽ không phát hiện được va chạm và cho phép cơ thể thứ hai để đi qua các cơ thể khác. (Trong ví dụ ma sát tĩnh, vận tốc yêu cầu là xung quanh 50.0, tôi chỉ quản lý để thực hiện thành công điều này một lần và do đó tôi không có hoạt hình mô tả nó).


Giải pháp 2

Đây là một giải pháp bổ sung, cảnh báo công bằng mặc dù: nó không đơn giản.

Theo nghĩa rộng, cách thức hoạt động này là kiểm tra xem cơ thể có bị kéo hay không dragBody, đã va chạm với một cơ thể tĩnh và nếu chuột đã di chuyển quá xa mà không dragBodytheo dõi. Nếu nó phát hiện ra rằng sự tách biệt giữa chuột và dragBodyđã trở nên quá lớn, nó sẽ loại bỏ trình lắng nghe sự kiện và thay thế nó bằng một chức năng mousemove khác , . Chức năng này kiểm tra xem chuột có quay trở lại trong phạm vi gần nhất định của trung tâm cơ thể không. Thật không may, tôi không thể làm cho phương thức tích hợp hoạt động chính xác nên tôi phải đưa nó trực tiếp vào (một người hiểu biết hơn tôi trong Javascript sẽ phải tìm ra phương pháp đó). Cuối cùng, nếu một sự kiện được phát hiện, nó sẽ chuyển trở lại trình nghe bình thường .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>

Sau khi áp dụng sơ đồ chuyển đổi trình nghe sự kiện, các cơ quan bây giờ hoạt động giống như thế này

nhập mô tả hình ảnh ở đây

Tôi đã kiểm tra điều này khá kỹ lưỡng, nhưng tôi không thể đảm bảo nó sẽ hoạt động trong mọi trường hợp. Nó cũng lưu ý rằng mouseupsự kiện này không được phát hiện trừ khi chuột ở trong khung vẽ khi nó xảy ra - nhưng điều này đúng với bất kỳ mouseupphát hiện Matter.js nào vì vậy tôi đã không cố gắng khắc phục điều đó.

Nếu vận tốc đủ lớn, Resolversẽ không phát hiện ra bất kỳ va chạm nào và vì nó thiếu khả năng phòng ngừa dự đoán về hương vị của xung đột vật lý này, sẽ cho phép cơ thể đi qua, như được hiển thị ở đây.

nhập mô tả hình ảnh ở đây

Điều này có thể được giải quyết bằng cách kết hợp với Giải pháp 1 .

Một lưu ý cuối cùng ở đây, có thể áp dụng điều này cho chỉ một số tương tác nhất định (ví dụ: những tương tác giữa một cơ thể tĩnh và không tĩnh). Làm như vậy được thực hiện bằng cách thay đổi

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

đến (ví dụ như các cơ quan tĩnh)

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

Giải pháp thất bại

Trong trường hợp bất kỳ người dùng nào trong tương lai gặp phải câu hỏi này và thấy cả hai giải pháp đều không đủ cho trường hợp sử dụng của họ, đây là một số giải pháp tôi đã thử mà không hiệu quả. Hướng dẫn các loại cho những gì không làm.

  • Gọi mouse.mouseuptrực tiếp: đối tượng bị xóa ngay lập tức.
  • Gọi mouse.mouseupqua Event.trigger(mouseConstraint, 'mouseup', {mouse: mouse}): ghi đè bởi Engine.update, hành vi không thay đổi.
  • Làm cho đối tượng được kéo tạm thời tĩnh: đối tượng bị xóa khi trở về trạng thái không tĩnh (cho dù thông qua Matter.Body.setStatic(body, false)hoặc body.isStatic = false).
  • Đặt lực lượng (0,0)thông qua setForcekhi tiếp cận xung đột: đối tượng vẫn có thể đi qua, sẽ cần phải được thực hiện Resolverđể thực sự hoạt động.
  • Thay đổi mouse.elementsang một canvas khác thông qua setElement()hoặc bằng cách thay đổi mouse.elementtrực tiếp: đối tượng bị xóa ngay lập tức.
  • Hoàn nguyên đối tượng về vị trí 'hợp lệ' cuối cùng: vẫn cho phép đi qua,
  • Thay đổi hành vi thông qua collisionStart: phát hiện va chạm không nhất quán vẫn cho phép vượt qua bằng phương pháp này


Cảm ơn rất nhiều vì những đóng góp của bạn! Tôi đã trao thưởng cho bạn tiền thưởng vì mặc dù giải pháp của bạn không hoàn hảo, nhưng nó chắc chắn chỉ ra con đường phía trước và bạn đặt một lượng lớn suy nghĩ và thời gian vào vấn đề này - Cảm ơn bạn !! Bây giờ tôi chắc chắn rằng vấn đề này cuối cùng là một lỗ hổng tính năng trong MatterJS và hy vọng cuộc thảo luận này sẽ đóng góp cho một giải pháp thực sự trong tương lai.
d13

@ D13 Cảm ơn, tôi đồng ý rằng vấn đề là cuối cùng trong mã cơ bản nhưng tôi vui vì tôi có thể nhận được một số semblance của giải pháp (s)
William Miller

0

Tôi đã có thể quản lý tính năng theo cách khác:

  • Không có "kéo" (vì vậy không có sự liên tục của điểm kéo với đối tượng được kéo Vs bù)
  • Trên mouseDown, vị trí con trỏ chuột đưa ra một vectơ vận tốc được định hướng cho đối tượng theo dõi
  • Trên mouseUp đặt lại véc tơ vận tốc của bạn
  • Hãy để mô phỏng vật chất làm phần còn lại

1
Không phải đó là cách matter.jsxử lý kéo cơ thể rồi sao? từ đây "... giống như một con suối ảo gắn vào con chuột. Khi kéo ... lò xo được gắn [vào cơ thể] và kéo theo hướng chuột ..."
Ghost

Chỉ thiết lập vận tốc ngăn cản sự chồng chéo, sping buộc cơ thể thông qua người khác.
Mosè Raguzzini

Điều này thực sự có thể chỉ ra một giải pháp. Nếu tôi hiểu chính xác, điều đó có nghĩa là không sử dụng MatterJS được tích hợp trong MouseConstraint và cài đặt vận tốc của cơ thể theo cách thủ công dựa trên vị trí của chuột. Tuy nhiên, tôi không chắc chắn chính xác cách thức này sẽ được thực hiện, vì vậy, nếu ai đó có thể đăng chi tiết về cách căn chỉnh cơ thể với vị trí của chuột, mà không sử dụng setPocation hoặc ràng buộc, vui lòng làm.
d13

@ d13 Bạn vẫn dựa vào MatterJS Resolverđể quyết định phải làm gì với các cơ quan va chạm - đã xem qua mã đó một chút công bằng tôi hy vọng nó vẫn sẽ quyết định cho phép kéo qua trong nhiều trường hợp ..... có thể hoạt động nếu bạn cũng đã triển khai phiên bản của riêng bạn solveVelocitysolvePositiontại thời điểm đó, bạn vẫn đang thực hiện thủ công những gì bạn muốn MatterJS xử lý trực tiếp ....
Ghost

0

Để kiểm soát va chạm khi kéo, bạn cần sử dụng bộ lọc va chạmcác sự kiện .

Tạo cơ thể với mặt nạ lọc va chạm mặc định 0x0001. Thêm bắt startdragenddragcác sự kiện và đặt loại bộ lọc va chạm cơ thể khác nhau để tạm thời tránh va chạm.

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
Cảm ơn rất nhiều cho bạn bản demo tuyệt vời! Tôi thực sự đang cố gắng để đạt được hiệu ứng ngược lại: Tôi cần ngăn các cơ thể giao nhau khi người này bị kéo vào người khác.
d13

Xin lỗi Nếu tôi hiểu nhầm vấn đề. Bạn có thể làm rõ những gì bạn có nghĩa là bằng cách ngăn chặn các cơ quan giao nhau? Bạn đang cố gắng ngăn chặn việc kéo qua các vật thể khác khi lực tác dụng?
Temur Tchanukvadze

1
Trong trường hợp đó, đó là vấn đề mở và không thể thực hiện được nếu không mã hóa cứng để thực hiện CCD. Hãy xem: github.com/liabru/matter-js/issues/5
Temur Tchanukvadze

0

Điều này dường như có liên quan đến vấn đề 672 trên trang GitHub của họ, điều này dường như cho thấy điều này xảy ra do thiếu Phát hiện va chạm liên tục (CCD).

Một nỗ lực khắc phục điều này đã được thực hiện và mã cho nó có thể được tìm thấy ở đây nhưng vấn đề vẫn còn mở nên có vẻ như bạn có thể cần phải chỉnh sửa công cụ để tự xây dựng CCD vào nó.


1
Cảm ơn câu trả lời của bạn! Tôi đã xem xét điều này nhưng tôi tin rằng đó không phải là vấn đề về CCD mà là vấn đề "Điều gì xảy ra khi một lực không thể ngăn cản gặp một trở ngại không thể di chuyển?" Bằng cách nào đó tôi cần tìm ra cách vô hiệu hóa các lực để ngăn các cơ thể giao nhau.
d13
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.