J'utilise MatterJs pour un jeu basé sur la physique et je n'ai pas trouvé de solution au problème d'empêcher les corps d'être traînés de force par la souris à travers d'autres corps. Si vous faites glisser un corps dans un autre corps, le corps que vous faites glisser peut se forcer dans et à travers l'autre corps. Je cherche un moyen fiable de les empêcher de se croiser. Vous pouvez observer cet effet dans n'importe quelle démo MatterJS en sélectionnant un corps avec la souris et en essayant de le forcer à travers un autre corps. Voici un exemple typique:
https://brm.io/matter-js/demo/#staticFriction
Malheureusement, cela interrompt tous les jeux ou simulations en fonction du glisser-déposer. J'ai tenté de nombreuses solutions, comme briser la contrainte de la souris en cas de collision ou réduire la rigidité de la contrainte, mais rien qui fonctionne de manière fiable.
Toutes les suggestions sont les bienvenues!
Je pense que la meilleure réponse ici serait une refonte importante du module _Matter.Resolver
_ pour implémenter l'évitement prédictif des conflits physiques entre tous les corps. Tout ce qui est court est garanti pour échouer dans certaines circonstances. Cela étant dit, ce sont deux "solutions" qui, en réalité, ne sont que des solutions partielles. Ils sont décrits ci-dessous.
Cette solution présente plusieurs avantages:
L'idée derrière cette approche est de résoudre le paradoxe de ce qui se passe " quand une force imparable rencontre un objet inamovible" en rendant la force stoppable. Ceci est activé par le _Matter.Event
_ beforeUpdate
, qui permet de contraindre la vitesse absolue et l'impulsion (ou plutôt positionImpulse
, qui n'est pas vraiment une impulsion physique) dans chaque direction. limites définies par l'utilisateur.
_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>
_
Dans l'exemple, je limite les velocity
et positionImpulse
dans x
et y
à une magnitude maximale de _25.0
_. Le résultat est montré ci-dessous
Comme vous pouvez le voir, il est possible d'être assez violent en traînant les corps et ils ne passeront pas entre eux. C'est ce qui distingue cette approche des autres: la plupart des autres solutions potentielles échouent lorsque l'utilisateur est suffisamment violent avec son glissement.
Le seul inconvénient que j'ai rencontré avec cette méthode est qu'il est possible d'utiliser un corps non statique pour frapper un autre corps non statique suffisamment fort pour lui donner une vitesse suffisante au point où le module Resolver
ne pourra pas détecter la collision et permettre au deuxième corps de passer à travers d'autres corps. (Dans l'exemple de frottement statique, la vitesse requise est d'environ _50.0
_, je n'ai réussi à le faire qu'une fois avec succès, et par conséquent je n'ai pas d'animation le représentant).
Il s'agit d'une solution supplémentaire, avertissement cependant: ce n'est pas simple.
En termes généraux, la façon dont cela fonctionne consiste à vérifier si le corps que vous faites glisser, dragBody
, est entré en collision avec un corps statique et si la souris s'est depuis déplacée trop loin sans que dragBody
ne le suive. S'il détecte que la séparation entre la souris et dragBody
est devenue trop importante, il supprime l'écouteur d'événement _Matter.js
_ _mouse.mousemove
_ de _mouse.element
_ et remplace avec une fonction de déplacement de souris différente, mousemove()
. Cette fonction vérifie si la souris est revenue à une certaine proximité du centre du corps. Malheureusement, je n'ai pas pu faire fonctionner correctement la méthode Matter.Mouse._getRelativeMousePosition()
intégrée, j'ai donc dû l'inclure directement (quelqu'un de plus compétent que moi en Javascript devra trouver celle-là). Enfin, si un événement mouseup
est détecté, il revient à l'écouteur mousemove
normal.
_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>
_
Après avoir appliqué le schéma de commutation de l'écouteur d'événements, les corps se comportent maintenant comme ceci
J'ai testé ce assez à fond, mais je ne peux pas garantir que cela fonctionnera dans tous les cas. Il convient également de noter que l'événement mouseup
n'est pas détecté à moins que la souris ne se trouve dans le canevas lorsqu'il se produit - mais cela est vrai pour toute détection Matter.js mouseup
, donc je n'ai pas essayé de répare ça.
Si la vitesse est suffisamment grande, Resolver
ne détectera aucune collision et, comme il manque de prévention prédictive de cette saveur de conflit physique, permettra au corps de passer, comme illustré ici.
Cela peut être résolu en combinant avec la solution 1 .
Une dernière remarque ici, il est possible de l'appliquer uniquement à certaines interactions (par exemple celles entre un corps statique et un corps non statique). Pour ce faire, il faut changer
_if (bodies[i] != dragBody && Matter.SAT.collides(bodies[i], dragBody).collided == true) {
//...
}
_
à (par exemple des corps statiques)
_if (bodies[i].isStatic == true && bodies[i] != dragBody &&
Matter.SAT.collides(bodies[i], dragBody).collided == true) {
//...
}
_
Dans le cas où de futurs utilisateurs rencontreraient cette question et trouveraient les deux solutions insuffisantes pour leur cas d'utilisation, voici quelques-unes des solutions que j'ai essayées et qui n'ont pas fonctionné. Un guide en quelque sorte pour ne pas faire.
mouse.mouseup
_ directement: objet supprimé immédiatement.mouse.mouseup
_ via Event.trigger(mouseConstraint, 'mouseup', {mouse: mouse})
: remplacé par _Engine.update
_, comportement inchangé.Matter.Body.setStatic(body, false)
ou _body.isStatic = false
_).(0,0)
_ via setForce
à l'approche d'un conflit: l'objet peut toujours passer, devrait être implémenté dans Resolver
pour fonctionner réellement.mouse.element
_ vers un canevas différent via setElement()
ou en mutant _mouse.element
_ directement: objet supprimé immédiatement.collisionStart
: la détection de collision incohérente permet toujours de passer avec cette méthodeCela semble être lié à problème 672 sur leur page GitHub qui semble suggérer que cela se produit en raison d'un manque de détection de collision continue (CCD).
Une tentative de remédier à cela a été faite et le code pour cela peut être trouvé ici mais le problème est toujours ouvert, il semble que vous ayez besoin de modifier le moteur pour y intégrer vous-même CCD.
Pour contrôler les collisions lorsque vous les faites glisser, vous devez utiliser filtre de collision et événements .
Créer des corps avec par défaut masque de filtre de collision0x0001
. Ajoutez des événements catch startdrag
et enddrag
et définissez un corps différent catégorie de filtre de collision pour éviter temporairement les collisions.
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>
J'aurais géré la fonctionnalité d'une autre manière: