Myślę, że najlepszą odpowiedzią na to pytanie będzie znaczący przegląd Matter.Resolver
moduł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 velocity
i positionImpulse
w x
i y
do 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 Resolver
moduł 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 dragBody
nie zderzyło się ze statycznym ciałem i czy myszka odtąd zbyt daleko się posunęła, nie dragBody
podążając za nim. Jeśli wykryje, że rozdzielenie między myszą a dragBody
stał 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.mousemove
mouse.element
mousemove()
Matter.Mouse._getRelativeMousePosition()
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>
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 mouseup
zdarzenie nie jest wykrywane, chyba że mysz znajdzie się w obszarze roboczym, gdy wystąpi - ale dotyczy to każdego mouseup
wykrycia Matter.js, więc nie próbowałem tego naprawić.
Jeśli prędkość jest wystarczająco duża, Resolver
nie 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.mouseup
Bezpośrednie wywołanie : obiekt natychmiast usunięty.
- Dzwonienie
mouse.mouseup
przez 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 setForce
kiedy zbliża się konflikt: obiekt może nadal przechodzą, będą musiały być wdrożone w Resolver
celu faktycznie pracują.
- Zmiana
mouse.element
na inne płótno poprzez setElement()
lub poprzez mouse.element
bezpoś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ę