Myślę, że najlepszą odpowiedzią na to pytanie będzie znaczący przegląd Matter.Resolvermodułu w celu wdrożenia predykcyjnego unikania fizycznych konfliktów między dowolnymi ciałami. Jest coś brakuje, że gwarantowane niepowodzenie w pewnych okolicznościach. Mówi się tutaj o dwóch „rozwiązaniach”, które w rzeczywistości są tylko rozwiązaniami częściowymi. Są one przedstawione poniżej.
Rozwiązanie 1 (aktualizacja)
To rozwiązanie ma kilka zalet:
- Jest bardziej zwięzły niż Rozwiązanie 2
- Tworzy to mniejszą powierzchnię obliczeniową niż Rozwiązanie 2
- Przeciąganie nie jest przerywane tak, jak w rozwiązaniu 2
- Może być nieniszczący w połączeniu z rozwiązaniem 2
Ideą tego podejścia jest rozwiązanie paradoksu tego, co dzieje się „ kiedy siła nie do powstrzymania napotyka nieruchomy obiekt ” poprzez uczynienie siły możliwą do zatrzymania. Jest to możliwe dzięki temu Matter.Event beforeUpdate, który pozwala na ograniczenie bezwzględnej prędkości i impulsu (a raczej positionImpulse, który tak naprawdę nie jest impulsem fizycznym) w każdym kierunku w granicach określonych przez użytkownika.
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>
Na przykład ja jestem jego ograniczania velocityi positionImpulsew xi ydo maksymalnej wielkości 25.0. Wynik pokazano poniżej

Jak widać, przeciąganie ciał może być dość gwałtowne i nie będą się one przenikać. Oto, co wyróżnia to podejście od innych: większość innych potencjalnych rozwiązań zawodzi, gdy użytkownik jest wystarczająco gwałtowny podczas przeciągania.
Jedyną wadą, jaką napotkałem przy tej metodzie, jest to, że można użyć ciała niestatycznego, aby uderzyć inne ciało niestatyczne wystarczająco mocno, aby zapewnić mu wystarczającą prędkość do punktu, w którym Resolvermoduł nie wykryje kolizji i pozwoli drugie ciało, aby przejść przez inne ciała. (W przykładzie tarcia statycznego jest wymagana wymagana prędkość 50.0, udało mi się to zrobić tylko raz, a zatem nie mam animacji przedstawiającej to).
Rozwiązanie 2
Jest to dodatkowe rozwiązanie, jednak uczciwe ostrzeżenie: nie jest proste.
Mówiąc ogólnie, sposób ten polega na sprawdzeniu, czy przeciągane ciało dragBodynie zderzyło się ze statycznym ciałem i czy myszka odtąd zbyt daleko się posunęła, nie dragBodypodążając za nim. Jeśli wykryje, że rozdzielenie między myszą a dragBodystał się zbyt duży usuwa detektor zdarzeń z i zastępuje ją inną funkcją mousemove, . Ta funkcja sprawdza, czy mysz powróciła do określonej odległości od środka ciała. Niestety nie udało mi się sprawić, by wbudowana metoda działała poprawnie, musiałem więc uwzględnić ją bezpośrednio (ktoś bardziej obeznany z JavaScriptem będzie musiał to rozgryźć). Wreszcie, jeśli zostanie wykryte zdarzenie, przełącza się z powrotem na normalny odbiornik.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>
Po zastosowaniu schematu przełączania nasłuchiwania zdarzeń ciała zachowują się teraz mniej więcej tak

Przetestowałem to dość dokładnie, ale nie mogę zagwarantować, że zadziała w każdym przypadku. Należy również zauważyć, że mouseupzdarzenie nie jest wykrywane, chyba że mysz znajdzie się w obszarze roboczym, gdy wystąpi - ale dotyczy to każdego mouseupwykrycia Matter.js, więc nie próbowałem tego naprawić.
Jeśli prędkość jest wystarczająco duża, Resolvernie wykryje żadnej kolizji, a ponieważ nie ma predykcyjnego zapobiegania temu smakowi fizycznego konfliktu, pozwoli ciału przejść, jak pokazano tutaj.

Można to rozwiązać, łącząc się z rozwiązaniem 1 .
I ostatnia uwaga: można to zastosować tylko do niektórych interakcji (np. Między ciałem statycznym i niestatycznym). Robi się to poprzez zmianę
if (bodies[i] != dragBody && Matter.SAT.collides(bodies[i], dragBody).collided == true) {
//...
}
do (np. ciał statycznych)
if (bodies[i].isStatic == true && bodies[i] != dragBody &&
Matter.SAT.collides(bodies[i], dragBody).collided == true) {
//...
}
Nieudane rozwiązania
W przypadku, gdy przyszli użytkownicy napotkają to pytanie i stwierdzą, że oba rozwiązania są niewystarczające dla ich przypadku użycia, oto niektóre rozwiązania, które próbowałem, które nie działały. Przewodnik po tym, czego nie robić.
mouse.mouseupBezpośrednie wywołanie : obiekt natychmiast usunięty.
- Dzwonienie
mouse.mouseupprzez Event.trigger(mouseConstraint, 'mouseup', {mouse: mouse}): zastąpione przez Engine.update, zachowanie niezmienione.
- Uczynienie przeciągniętego obiektu tymczasowym statycznym: obiekt usuwany po powrocie do niestatycznego (za pośrednictwem
Matter.Body.setStatic(body, false)lub body.isStatic = false).
- Ustawianie siły
(0,0)poprzez setForcekiedy zbliża się konflikt: obiekt może nadal przechodzą, będą musiały być wdrożone w Resolvercelu faktycznie pracują.
- Zmiana
mouse.elementna inne płótno poprzez setElement()lub poprzez mouse.elementbezpośrednią mutację : obiekt natychmiast usuwany.
- Cofanie obiektu do ostatniej „prawidłowej” pozycji: nadal umożliwia przejście,
- Zmień zachowanie poprzez
collisionStart: niespójne wykrywanie kolizji nadal pozwala przejść przez tę metodę