4
\$\begingroup\$

There is this strange issue with the collision in a game I am making. When the player is slightly lower than a block it is colliding with from the side, the player ends up clipping to the top of the block rather than sliding along the side of it down. Do you have an idea how to fix it?

If that description didn't help you at all you can reproduce it here (goes to the website with the game on it). (Use WASD/Space/Arrow Keys to move) To reproduce, stand on the single block and hold the right move button. You will fall off the edge, hit the side of the block, and get clipped to the top of it. You are meant to fall onto the block below instead. (The words and colors are debug info)

The code below is what handles the collision detection. If you need the rest of the code, you can view the source of the page linked above.

this.doCollision = function() {
var tx = Math.floor(this.x),
    ty = Math.floor(this.y),
    nx = this.x % 1,
    ny = this.y % 1,
    cell = this.cell = levelStorage.currentLevel.getTileAtPosition(tx, ty),
    cellRight = this.cellRight = levelStorage.currentLevel.getTileAtPosition(tx + 1, ty),
    cellDown = this.cellDown = levelStorage.currentLevel.getTileAtPosition(tx, ty + 1),
    cellDiag = this.cellDiag = levelStorage.currentLevel.getTileAtPosition(tx + 1, ty + 1);

if(this.vy > 0) { // Ground collision
// The !cell checks return if the cell exists or not (null)
  if((cellDown && !cell) || (!cellRight && cellDiag && nx)) {
    this.y = ty;
    this.vy = 0;
    this.falling = false;
    this.jumping = false;
    ny = 0
  }
}else
if(this.vy < 0) { // Ceiling collision
  if((!cellDown && cell) || (cellRight && !cellDiag && nx)) {
    this.y = ty + 1;
    this.vy = 0;
    cell = this.cell = cellDown;
    cellRight = this.cellRight = cellDiag;
    ny = 0;
  }
}
if(this.vx > 0) { // Right collision
  if((cellRight && !cell) || (!cellDown && cellDiag && ny)) {
    this.x = tx;
    this.vx = 0;
    nx = 0;
  }
}else
if(this.vx < 0) { // Left collision
  if((!cellRight && cell) || (cellDown && !cellDiag && ny)) {
    this.x = tx + 1;
    this.vx = 0;
    cell = this.cell = cellRight;
    cellDown = this.cellDown = cellDiag;
    nx = 0;
  }
}
this.falling = !(cellDown || (nx && cellDiag)); // If player isn't on ground
}
}

Thanks for the help!

EDIT: I should note this also happens with the bottom of blocks as well. The same principles apply as clipping to the top, except you clip to the bottom instead.

\$\endgroup\$
1
\$\begingroup\$

I'm guessing that vx and vy are velocities...

Since you check for vy floor and ceiling first, then any down-and-right collision gets corrected vertically first, then horizontally, so you end up running rooftop-to-rooftop.

In this particular case, maybe checking for vx left and right first might just fix it. Or handle it as an else clause, since once you correct horizontally (hitting the wall) it shouldn't continue to also handle it vertically.

Other strategies that apply for this kind of collision checking include:

  • Look at x- and y-penetration, and fix the smaller of the two
  • Never "correct" a motion more than you've moved in that tick
  • Handle x- and y-penetrations sequentially

None of these is always the right thing to do, each has their place depending what kind of a world you're trying to simulate or present.

P.S. Having the demo live was very useful to understand what you were describing. ++.

\$\endgroup\$
  • \$\begingroup\$ I have tried all the suggestions you mentioned but none of them seem to be working out. By checking horizontal collisions before vertical the player ends up phasing through walls on the vertical axis. By handling it as an else statement the player ends up falling through the ground or phasing through walls due to only one type of collision being detected. By checking which axis penetrates the wall least and only checking that, the player ends up going inside blocks. Thank you for the suggestions though. \$\endgroup\$ – Esophose Oct 19 '15 at 15:22
  • \$\begingroup\$ Also, to consider, for every motion, if the player is smaller than a block, you may have to resolve as many as 3 collisions. \$\endgroup\$ – david van brink Oct 19 '15 at 18:27
1
\$\begingroup\$

You cannot determine the collision response by the velocity in this case.

You must separate the collision between the player and a block using the minimum translation distance. This is how you perform real collision: http://gamedevelopment.tutsplus.com/tutorials/collision-detection-using-the-separating-axis-theorem--gamedev-169

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.