Jump to content

The 'moving jumpthrough platform' sum up thread - help very much appreciated


Recommended Posts

since i discussed this topic in at least 2 other threads more or less "off topic" i thought i would start this one to collect all ideas and approaches to this problem.


i created 3 possible solutions at this time but all of them have their obstacles


example 1:



in this example i use the postbroadphase callback to determine the velocity of the player and decide not to collide if the player is moving upwards to the platform


problem: if the velocity goes below the critical point while inside a platform the player will shoot out of the platform with the equations maxForce


example 2:


2 sensors (upper/lower) deactivate or reactivate the collision for the players mainshape.. very reliable but far from perfect


problem: if you touch a platform while standing on ground you will almost fall through the ground.  you could also try to cut the platform (touch it only with the upper sensor) to deactivate the collision and then jump through a thin wall or be invincible to fireballs for example until the next touch down



example 3:


here i'm manipulating the contact equation. so a contact still happens but i remove the force if the body is touching a platform and coming from below..  this also kinda works but..


problem:  jump through is only possible while applying a force..  you can "slide" through the platforms from the sides.. and so on 



example 4:


this one is using the onPresolve method..   this way it is possible to cancel the equation when the player is moving upwards..  (and still get all the information of the possible collision to make further decisions)


problem:  when the player reaches almost zero velocity and is still inside the platform it will get stuck.. for some reason no force is applied not even gravity (which would be most natural way to get him out of the platform. for the moment i try to catch this case and manually apply a velocity) 





you see.. several approaches but none of them is really perfect..  :unsure:


i would like to hear your ideas  (browse the code with "rightclick view source)) maybe together we are able to find a perfect solution  :)


i'm happy about every idea that could prevent some of the mentioned problems and of course i'm also interested in completely new approaches

Link to comment
Share on other sites

You're touching a complex topic as far as I know. I remember when I read the following article- very interesting but I immediately felt all the problems that I would get along with the mentioned ones :) It's a iforce2d reading- I highly recommend their tutorials. See here for one way walls: https://www.iforce2d.net/b2dtut/one-way-walls

It's box2d, but thanks to schteppe p2 is designed a lot like box2d. You could also broaden your search efforts to box2d resources if you haven't already done so.


Another thing you could two: Ask schteppe himself on github- or maybe he sees that post, as he's a member of the forum since lately.

Your three versions are fine. I would have approached it the same way. I would look deeper into the contact equation, maybe you can try other calculations/constraints (see the iforce article)


Regards George


Corresponding video to the platform example

Link to comment
Share on other sites

thx for the input george..  i have difficulties translating the given box2d howto to phaser :(


but i went with your suggestion and refined example #3..  

it comes down to this: (much cleaner now)

function checkIfShouldCollide(bodyB,shapeA,shapeB,contactEquation){    var yAxis = p2.vec2.fromValues(0, 1);    var c = contactEquation[0]    var y = p2.vec2.dot(c.normalA, yAxis); // Normal dot Y-axis    if (bodyB && bodyB.sprite && bodyB.sprite.key == 'platform' && y >= 1){ c.maxForce=0; }    else { c.maxForce=1.7976931348623157e+308;  /*seems to be the default maxForce */  }}

it works really good except one special case where the player stops colliding and is still inside a platform (it's easy to try .. jump into the upper right platform)...  

no collision event is sent anymore so i don't know how to catch this case..


unfortunately onEndContact()  which i think would be fine to catch this special case is never fired..  i already asked in another thread about this but got no answers atm.



also i don't know how to properly disable the collision so i am using the maxForce property..  since i have absolutely no idea what i'm doing i really hope i can get schteppe to read this thread  :rolleyes:



Link to comment
Share on other sites

I've fought against this problem for a long time now.


I tried, as I saw you did in the past too, valueerror, to use Tweens to move sprites using its 'body.velocity' for movement. A solution that works... until it doesn't.


(For other people, you can, in fact, tween body.velocity. However, the problem becomes in resetting the velocity or reversing it. Moving objects have inertia and there isn't a clean solution to resetting the velocity of a moving object and setting a new one -- even with a tween -- without messing up collisions during that interim time.)


What I eventually settled on doing was creating subclasses of Phaser.Sprite whose sole purpose were to act as elevators (up and down) or conveyors (side to side).


Here is my example code:

However, there is still a problem with this approach. Occasionally, the sprites will 'stick' before moving again. It's 99% of what I want and have messed around with it a few times to figure out that last little problem, but am not sure exactly what is causing it in the first place.


Oh, and those two examples are using the Arcade physics system. It shouldn't be too hard to port them to P2, though.

Link to comment
Share on other sites

thx videlais for those two examples!  i think they work really great.. maybe you could avoid sticking to the edges by setting x (or y) of the platform back (into the direction it came from) a few pixels when it touches the "to" or "from" position right before you change the velocity direction ?


the overall approach seems to be quite the same on first sight..  .


i programmed the movement of the platforms in a very similar way like you did ... my 'to' and 'from' are called 'leftbounds' and 'rightbounds' - the only difference is the creation of the platforms but that's not the problem..  IMHO the whole movement is no problem at all (at least with p2 there is no 'stick' to the worldbounds problem as i saw in your example)


unfortunately  porting the "jumpthrough" to p2 seems to be a big problem..   in p2 there is no option to set checkCollision.down to false - so you have to do it manually.. and that's what i'm trying right now.. and failing  :rolleyes:

Link to comment
Share on other sites

Hi valueerror,

great to see progress on this event that you're struggling again. But maybe this is simply owed to a bug in p2 with endContact ?

Check this https://github.com/schteppe/p2.js/issues/90





also i don't know how to properly disable the collision so i am using the maxForce property..  since i have absolutely no idea what i'm doing i really hope i can get schteppe to read this thread


I wondered all the time about that maxForce in your method. Just now I saw you last sentence. You can't cancel the equation in beginContact (as it's only dispatched once, namely at the beginning ;) ). You have to use the preSolve event! Maybe that will fix it, you can remove both other listeners and ignore the endContact bug ?




Link to comment
Share on other sites

1. There are three events:
game.physics.p2.world.on("beginContact", onBeginContact) -- oncegame.physics.p2.world.on("endContact", onEndContact) -- during the whole contactgame.physics.p2.world.on("preSolve", onPresolve) -- once

preSolve is dispatched during the whole contact. beginContact only once at the beginning

2. You can cancel a contact equation by setting enabled to false:
contactEquation.enabled = false

This will prevent that it's being interpreted at all- so no need to emulate it with maxForce

3. You can't cancel it once in the beginContact event.
p2 is workind a little different than box2d. I also made this error before.
The contact equations are generated through the whole collision. You will get two of them with beginContact and endContact. The rest is accessible within preSolve.
PreSolve is the state before a contact equation is beging 'solved', that is: apply force to all involved elements to solve the collision conflict.
You can technically set contactEquation.enabled = false during beginContact. But without a visible effect. You only cancel the first equation. In the next step, the next equation is generated. As nothing changed, the two elements are still overlapping and p2 still tries to resolve this conflict. It doesn't know anymore about your enabled = false from the first contact equation as it's not stored over multiple contact equations. It's being reset before each equation. That's the thing we did not expect.
4 Your way of doing it with maxForce is risky
This surprisingly works. I guess it's working because a contact equation is reused and you maxForce value is not being reset. So you are using a value that persists over all contact equations (like we expected it from ce.enabled = false). But you will get problems when you use this technique at second place. You will run in conflict as the same maxForce is access and change by two different places. That's where difficult bugs are getting born :)
This really bugged me before too so I asked to change the behaviour of ce.disabled so it lasts over the whole contact
What you can do ? Try to persist the state of the cancelled collision at least per shape.
onBeginContact://introduce you own attributeshape.passthrough = trueonPresolve:if (shape.passthrough){  ce.enabled = false}onEndContact:if (shape.passthrough){  shape.passthrough = false}

But wait: That's what I did and it was the moment where I encountered the mentioned bug with endContact lately ;)


Regards George
Link to comment
Share on other sites

hey george..  thx again..  veeeery slowly i start to understand what i'm doing here  ;)


just for your information..  i tried phaser 203 and the onEndContact event is working and fired on every contactEND (as it should be) in 205 (the new p2.js)  nothing...


but the one thing that still bothers me is that state where the player gets stuck inside the platform.. when the player reaches zero velocity and is still inside the platform it will not fall down..  why?  the collision is canceled and gravity should do it's job!!  onEndContact is not fired (even with phaser 203 it isn't)


if you look at my first post you will see that i added example nr4 .. i did what you suggested and used onPresolve to do the same thing.. 

function onPresolve(presolve){  if (c = presolve.contactEquations[0]){    var yAxis = p2.vec2.fromValues(0, 1);    var y = p2.vec2.dot(c.normalA, yAxis);        if (c.bodyB.parent.sprite && c.bodyB.parent.sprite.key == 'platform' && y >= 0){        c.enabled = false        // catch the case where mario gets stuck inside a platform        if (c.bodyA.parent.velocity.destination[1] < 10 ){            console.log('velocity almost 0 - stuck inside the platform - gravity where are you?')            c.bodyA.parent.velocity.destination[1]=-10;  //push down        }     }  }}

i can now use c.enabled = false; which is good but the behaviour is exactly the same..  the player still gets stuck inside the platform..  the main difference? onPresolve is fired again and again (as you said) and i am able to do further calculations like i did when checking for the velocity)


but i have the feeling that there is a better approach to rule out this bug..

i tried to use the platforms aabb.lowerBounds and aabb.upperBounds and the player position to find out if the player is inside the platform but this didn't work well - also i'm unsure what would be the best thing to do..  at the moment i push the player back down..  i could add further calculations and decide to push up when the player reached over 80% of the platforms height for example..  or do something completely different ?! 

Link to comment
Share on other sites

Can you record a small screencast how to reproduce the case or better could you prepare a demo where you could trigger this behaviour by a single click. I'm not able to get the player stuck and if I could it would be tedious to do so again.


A better testable version of this would be to hold the platforms, reset the player to a predefined position and apply a force in the right direction on a key press or mouse click. Could you prepare something like this ? This would help to include others in the discussion and you would be well prepared to report this behaviour to schteppe- as this could also manifest as a bug in p2.




Link to comment
Share on other sites



just jump and get stuck  :)

i made mario jump only once when touching the ground and jump exactly into the platform..  


edit: you couldn't get him to get stuck because i tried to catch that case ^^  (so it seems to work.. haha.. great!)

Link to comment
Share on other sites

i just remembered that there is also a friction equation so i canceled the friction equation too.. now that's interesting..  


check this out:  http://test.xapient.net/phaser/ALL/moving-jumpthrough-platform-4-nofrictionEq.html


function onPresolve(presolve){  if (presolve.contactEquations[0]){    c = presolve.contactEquations[0]    f = presolve.frictionEquations[0];    var yAxis = p2.vec2.fromValues(0, 1);    var y = p2.vec2.dot(c.normalA, yAxis);        if (c.bodyB.parent.sprite && c.bodyB.parent.sprite.key == 'platform' && y >= 0){        c.enabled = false        f.enabled = false    }  }





if i combine this with a small course correction it feels already really good

  if (c.bodyB.parent.sprite && c.bodyB.parent.sprite.key == 'platform' && y >= 0){        c.enabled = false        f.enabled = false        if (c.bodyA.parent.velocity.destination[1] < 15 ){            console.log('velocity < 15 - still inside the platform - course correction!')            c.bodyA.parent.velocity.destination[1]-= 1;        }     }
Link to comment
Share on other sites

Nice find! That slip through feels really good. The only thing which is left- and that I can reproduce frequently: I can walk against the sides of a platform and as long as I keep the arrow pressed mario sticks at the side of the platform. This is due to the frequent call of moveLeft. Maybe you should make a difference when on the ground or in the air (state = jump). Maybe change the velocity not so much in the air, use force instead of velocity(which is modified through moveLeft). Only ideas.


So we're done with it I guess ? Good Job!

Link to comment
Share on other sites

well..  i don't know if it's good but it is definitely good enough for now...


here is the final example where i combine the disabling of the equations with the velocity check..  (using a smaller number than in the given code example so it feels right)




i also added a little bit of air friction to make the jumps more realistic but this  :angry: **** 'sticktothesideofobjectswhenapplyingforce' problem is something for another thread...


i can't just disable all xAxis-forces when up in the air.. this leads to several other problems  (a little bit of unrealistic movement needs to remain in a plattformer otherwise it's no fun..  i need to be able to jump and then decide to not jump and steer back for example..  


so IMHO i need another contactEquation ^^  - find out if i'm coming from the left or the right and disallow using more force into that direction..  but i will start a seperate thread for this  :ph34r:

Link to comment
Share on other sites

here is the code..  just in case my server goes down  :)


function onPresolve(presolve){    for (var i = 0; i < presolve.contactEquations.length; i++) {        c = presolve.contactEquations[i]        f = presolve.frictionEquations[i];        if (c.bodyA.parent.sprite && c.bodyA.parent.sprite.name === 'mario'){   //define here who is allowed to jump through            var yAxis = p2.vec2.fromValues(0, 1);            var y = p2.vec2.dot(c.normalA, yAxis);            if (c.bodyB.parent.sprite && c.bodyB.parent.sprite.key == 'movingplatforms' && y >= 0){  // check for jumpthrough object // check if moving upwards                c.enabled = false   //disable contactEquation                f.enabled = false   //disable frictionEquation (solves the stuckInPlatform problem)                if (c.bodyA.parent.velocity.destination[1] < 15 ){ // velocity < 15 - still inside the platform                    c.bodyA.parent.velocity.destination[1]-= 0.5;     // course correction!                }             }        }    }}

EDIT:  the former code example only worked if there is just one object (player) that collides with other objects.. if there are more objects (other enemies for example) you will need to cycle through all contact equations.. i edited the code above


if someone is able to tune this example and make it even better..  please report here ... thx!

Link to comment
Share on other sites

btw.   i solved the "stick to other object sides when a force is applied"  with a little workaround..  i now also check if mario is up in the air.. and if so i change his material to "ice"  with zero friction..  the moment he touches the ground he becomes normal playerMaterial again..  first tests look good :)

Link to comment
Share on other sites

  • 1 year later...

Hey Valueerror,


thank you a lot for this topic, saved me loads of time!


I got the same issue but my character has more than one shape, so I made a little modification to the code you posted, that it works with multiple shapes and materials (the code is in es6):

onPresolveContact(presolve){    let disableContact = false;    let confirmedContactIndexes = [];    presolve.contactEquations.forEach((contactEquation, index) => {        // checks if bodyA or bodyB is equal this character body data and the colliding body is in jumpThroughMaterial Array        if(contactEquation.bodyA === this.entity.body.data && contactEquation.bodyB.parent.data.shapes[0].material && this.jumpThroughMaterial.indexOf(contactEquation.bodyB.parent.data.shapes[0].material.name) !== -1 || contactEquation.bodyB === this.entity.body.data && contactEquation.bodyA.parent.data.shapes[0].material && this.jumpThroughMaterial.indexOf(contactEquation.bodyA.parent.data.shapes[0].material.name) !== -1){            // register all contacts which are in connection with this character body and eventually get disabled, if disableContact === true            confirmedContactIndexes.push(index);            // disable contactEquation Conditions            if(this.entity.body.velocity.y < 0){                // disable when character moves upwards                disableContact = true;            }else if(contactEquation.bodyA === this.entity.body.data && contactEquation.contactPointA[1] >= -0.5){                // disable if bodyA (character body) is not above bodyB                disableContact = true;            }else if(contactEquation.bodyB === this.entity.body.data && contactEquation.contactPointB[1] <= 0.5){                // disable if bodyB (character body) is not above bodyA                disableContact = true;            }        }    });    if(disableContact){        confirmedContactIndexes.forEach((index) => {            if(presolve.contactEquations[index]){                presolve.contactEquations[index].enabled = false; //disable contactEquation            }            if(presolve.frictionEquations[index]){                presolve.frictionEquations[index].enabled = false; //disable frictionEquation (solves the stuckInPlatform problem)            }        });    }}


  1. "this.entity.body.data" refers to the sprite -> p2 body object
  2. "this.jumpThroughMaterial" is an array holding all material names which can be jumped through
  3. Using contactPoint instead of normalA seemed to allow more fine grained decisions... (what do you think?)
  4. at contactPoint A/B[1], it seemed to work the smoothest with 0.5 to confirm contact (let me know if you find a better value here!?)
  5. All contactEquations meeting the conditions get disabled after collecting them into "confirmedContactIndexes" and at least having one condition triggering disableContact === true

I dropped the course correction, since that made me loads of troubles.




Link to comment
Share on other sites


  • Recently Browsing   0 members

    • No registered users viewing this page.
  • Create New...