liabru / matter-js

a 2D rigid body physics engine for the web ▲● ■
MIT License
16.95k stars 1.97k forks source link

Moving bodies accurately for a top down multiplayer game #157

Closed EskelCz closed 8 years ago

EskelCz commented 9 years ago

Hi, I can't find an example close to my use case and I have to admit I'm not very good at physics theory. The source code has so many interactions of similar (to me) properties that I kind of got lost.

I'd like to move a body based on mouse input, with no gravity and 'inertia' (nothing floating, it's all top-down moving characters and walls). When a body collides with another body, I need to know the length of the vector it actually moved until the collision (so I can send it to a server). Let's say I tried to move by x:100 and at distance 50 is a wall. I'd like to get a {x:50, y:0} vector. Also a reference to the colliding body would be really nice, for pushing things.

I tried 'setPosition' or 'translate' and looked at 'positionImpulse' value. The engine computes the behavior properly but the 'positionImpulse' doesn't reset to zero after the collision is cleared.

Also properties 'moving' and 'velocity' don't get updated, even though it I thought they should from the velocity description.

Maybe I'm using a wrong method altogether. Thanks for any guidance, I suspect more people might run into similar problems.

danielkcz commented 9 years ago

Hey, I am working with @EskelCz on the game. I think he didn't explained well what we need and some of the issues are already solved anyway.

Currently we have a rather simple problem. For a player body we use Bodies.circle with these settings:

        collisionBody.frictionAir = 0
        collisionBody.inverseInertia = 0

When these two bodies meet, they bounce off each other. We simply need them to stop moving in the direction of a collision, but they can still move around each other. I am rather weak at physics too, isn't there some option that would allow us to do that?

liabru commented 9 years ago

From the sounds of it I think you should look at the Body Manipulation demo and check out the source code. This shows some techniques for precisely controlling bodies.

formula1 commented 9 years ago

Id highly suggest you use impulses.

Onmousemove

On timestep

The reason this is better is setting positiom directly will cause bodies to overlap. Setting position will quite literally set position. And the collision resolver will not know how to handle it as the velocities that caused it are trchnically nonexistant. Set position is better for respawning/new level type situations where the dev is fully aware of what is and isnt around. You can also do a set position in a crowded space after finding an opponening but you will need to find an opening.

I dont know if this can be applied to matter js but i know its true for box2d and it seems as though matter may have taken heavy inspiration if not some of box2ds code itself

danielkcz commented 9 years ago

@formula1: That sounds really like a good thing to have. We are doing exactly that, using translate to move onto coordinates where the collision happens. I tried debugging deeply, but didn't really found the way how to influence this bounce off effect.

formula1 commented 9 years ago

https://github.com/liabru/matter-js/blob/master/examples/restitution.js

This is probably what your looking for. If you go to the restitution example of the demo you can essentially modify how much an object elastic/inelastic a collision is.

I assume when resitution should be 0

danielkcz commented 9 years ago

Yeah, I definitely tried that before, has no effect in this case. It's even set to 0 by default.

formula1 commented 9 years ago

Hm... Looking at the resolver code

I see the only place that restitution is being used is

Im confident its being handled correctly, though i havent calculated use cases. Heres what we know though

  1. Assuming its being handled correctly, the initial collision will be resolved and will cause the two bodies to move at the same velocity in the same direction
  2. The two bodies then have a new impulse set to ensure they are in the position they want
  3. This causes a new collision which should resolve to both velocities at the same speed

This is likely a bug. I don't believe box2d has this issue so ill have to test it out. But this may be a deeper issue

EskelCz commented 9 years ago

Restitution and bouncing is not an issue, the problem was in friction and I've solved it some time ago. The topic of this thread is collision control and and value precision/resets and weight application.

In particular these two questions:

  1. How do I create an object with such density/weight that it just barely moves when pushed (by intersecting position of another body). I've tried setting mass and/or density to really high values and it didn't seem to change a thing.
  2. How do I figure out if the object is still moving, or stopped by a collision? I'm changing the object's position in game-loop and after each update the body speed / velocity stays at 0. Should I compare positions before/after in the game loop itself? (then the game might think the object is stuck when browser drops animation frames) Or is there an internal collision body value for this somewhere?
formula1 commented 9 years ago

If you directly change position, no velocities will br applied. This will cause overlaps and make the collsion solver make innaccurate or messed up reaction forces.

By applying an impulse that subtracts the last velocity and adds the new velocity to the bodies center, you allow the collision solver to act the way it should

EskelCz commented 9 years ago

Ok so I tested using positionImpulse (directly, as I can't find any setter method) but it acts pretty much the same. It just goes faster, has a little inertia overshoot at the end of translation and some random rotation. That doesn't bother me but the velocity and speed values are still 0, before and after engine update.

formula1 commented 9 years ago

Ill try to take a look tonight.

formula1 commented 9 years ago

I understand now what you mean and honestly, its rather difficult to implement properly in Matter JS. Below was my code and I found that often the Body would end up at positions > +/- 5000 causing freezes in addition forces not working quite how I understand they should. I found that the closer I multiplied each of the Vectors by one, the more direct the velocity would be to the effect I think you desired. But to get to this, you need to increase the velocity iterations to the point where it will not blindly guess when presented a huge velocity change. I'll definitely say this was not my comfort zone though I thought it was as simple as I've done it in other areas. I was incorrect to tell you to "apply impulse" as there is no method for that from what I can tell from the docs. Hopefully this is a good example for you

<!doctype html>
<html>
    <head>
        <script src="https://raw.githubusercontent.com/liabru/matter-js/master/build/matter.min.js" ></script>
        <script type="text/javascript" >
            window.addEventListener('load', function(){

                var World = Matter.World,
                    Bodies = Matter.Bodies,
                        Vector = Matter.Vector,
                        Body = Matter.Body,
                        Engine = Matter.Engine,
                        Events = Matter.Events,
                        Mouse = Matter.Mouse;

                var engine = Matter.Engine.create(document.querySelectorAll('#canvas-container')[0], {
                    positionIterations: 10,
                    velocityIterations: 30,
                    enableSleeping: false,
                    metrics: { extended: true}
                });
                engine.timing.isFixed = true;
                var runner = Engine.run(engine);

                var movable = Bodies.circle(300, 300, 10, { frictionAir: 0.000, restitution: 0.0, density:1 });
                var canvas = engine.render.canvas;
                var mouse = Mouse.create(canvas);

                Events.on(engine, 'tick', function(){

                    var toSub = Vector.sub({x:0,y:0}, movable.velocity);
                    toSub = Vector.mult(toSub, 3/4);
                    Body.applyForce(movable, movable.position, toSub);

                    var impulse = Vector.sub(mouse.position, movable.position)
                    console.log("before vel: ", 10/runner.delta);
                    impulse =    Vector.mult(impulse, 3/4);
                    Body.applyForce(movable, movable.position, impulse);
                });
                engine.world.gravity.y = 0;
                engine.world.gravity.x = 0;
                engine.timing.timeScale = 1;
                World.add(engine.world, [
                    movable,
                    Bodies.circle(250, 250, 20, { frictionAir: 0.000, restitution: 0.0, density: 1000 }),
                ]);
            });
        </script>
    </head>
    <body>
        <div id="canvas-container"></div>
    </body>
</html>
EskelCz commented 9 years ago

That's a perfect example, thanks a lot for looking into it, I'll try it out on our engine. One more thing, how would I go about making the obstacle almost unmovable? So it can be pushed but only by a very little. (a heavy crate for example) I've tried mass and density values to no avail. (that was with the position update method)

formula1 commented 9 years ago

This should be able to handle density and weight as it uses forces rather than setting position directly. To make it actually immovable or something that wants to retain position http://brm.io/matter-js-docs/classes/Constraint.html

You want to specify a bodyA then specify pointB in world space (most likely body.position), with length of zero and the stiffness will be the how immovable it is

EskelCz commented 8 years ago

The simplest solution with forces I've found is this (each tick):

Body.applyForce body, body.position, {x: -body.velocity.x, y: -body.velocity.y} Body.applyForce body, body.position, {x: x, y: y}

Aleksey-Danchin commented 8 years ago

Each tick? Do you mean something:

Matter.Events.on(engine, 'afterUpdate', function () {
    Body.applyForce(body, body.position, {x: -body.velocity.x, y: -body.velocity.y});
    Body.applyForce(body, body.position, {x: x, y: y});
});
EskelCz commented 8 years ago

@Aleksey-Danchin: Exactly, that was coffeescript, sorry. We have our own game loop but that afterUpdate event should work as well I guess.

EskelCz commented 8 years ago

I just found out that this is not precise when moving multiple bodies at the same time. No idea why but they move faster despite having the same x, y input. :( Reverting back to Body.translate

liabru commented 8 years ago

@EskelCz are they the same mass?

singerbj commented 6 years ago

@liabru In regards to your initial response to this issue, I'm looking at the Body Manipulation Demo... but I'm not seeing how the example applies to moving bodies accurately for a top down multiplayer game.

I have successfully synchronized two instances of matterjs, but since one is the server and one is the client, they don't run at the same speeds. This causes the issue mentioned above (I think) where the host player moves slower than the client player despite having the same velocities. I'm struggling to find the right way to force all bodies velocities to be relative to actual time rather than the engine's tick interval (which I think is what's causing my problem? Correct me if I'm wrong). Do you have any guidance on this matter?