2D Platform edge collisions

Started by
5 comments, last by ravinDavin 12 years, 5 months ago
Hi All, I'm wondering if someone can help offer some insight into a very frustrating problem I'm having.

I've currently written a small platform test demo, but some of the collision detection is causing strange movement problems (teleporting to the tops of blocks, sticking to the sides etc.).

The player is moved every frame using inertia / velocity values, as such:

Player.x += speedX;
Player.y += speedY;


Before the player movement is updated, however, I run a collision check against all blocks in the level. Naturally, in the final version I'll be limiting the number of these checks to only nearby objects, but I need the theory to work first!

When testing collison, I first run a quick exclusion text to see if the player and block are going to intersect:


function WillIntersect(block)
{
var newX = Player.x + speedX;
var newY = Player.y + speedY;

if (Player.x > block.x + block.width || Player.y > block.y + block.height || Player.x + Player.width < clip.x || Player,y + Player.height < clip.y)
{
// no intersection, thus no collision
return false;
}
else
{
// the two object bounding boxes intersect in some way
return true;
}
}


Then, as a result of this check, I then move into a collision detection method that attempts to resolve the collision that occured.

The following rules persist for movement around the game level:

  • If no intersection was detected with any object, the speedX and speedY values will remain unmodified.
  • If the player falls onto the top of a block, the speedY value is zeroed-out and they are repositioned on top of the block with pixel precision
  • If the player hits the underside of a block, the speedY value is zeroed-out and they are repositioned directly underneath the block
  • If the player hits the left, or right side of a block, the speedX value is zeroed-out and they are repositioned to the relevant edge.

The problem I have is when a combination of these factors occur. When the player jumps sideways against a wall (or the edge of a platform), the player will 'stick' to the vertical surface because the code generating the 'push to top' or 'push to bottom' check is running and canceling out any other checks.

Video of the effect in question:
http://www.youtube.c...h?v=nFusvi8U_-A

Finally, here's the code I'm currently using for the collisions. I've been tearing my hair out over this for over a week now, and just need some fresh viewpoints - you know what it's like when you've been staring at the same problem for days on end!


function DoCollisions()
{
txtOutput.text = "";

var newX = Player.x + speedX;
var newY = Player.y + speedY;

for (var i = 0; i < Level.numChildren; i++)
{
var clip = Level.getChildAt(i);

if (getQualifiedClassName(clip) == "BLOCK")
{
if (WillIntersect(clip))
{
if (speedX > 0)
{
speedX = 0;
Player.x = clip.x - Player.width;
}
else if (speedX < 0)
{
speedX = 0;
Player.x = clip.x + clip.width;
}
else { }

if (speedY > 0)
{
speedY = 0;
Player.y = clip.y - Player.height;
}
else if (speedY < 0)
{
speedY = 0;
Player.y = clip.y + clip.height;
}
else { }
}
}
}
}


Thanks to anyone who can offer some advice here.
Advertisement
Try performing two checks, one for each component of the player's velocity. For example, first check if the player's y-velocity will place the player inside a block. If it will, then zero out the player's -velocity and reposition the player's y-position. Next, do the same for the player's x-velocity.
The problem here is that the program checks where to move the player depending on the players direction rather than which side it actually hits. What I mean is that if even if you move UP, and RIGHT onto the LEFT side of the square, the program thinks the player is moving into the corner since you do two checks.

If the player X speed is positive and it intersects - it's on the left side.
If the player Y speed is negative and it intersects - it's on the bottom.
So if both the player X speed is positive and Y speed is negative - it's on the left bottom corner.

You need to make sure it doesn't reposition the player's Y if the player's X is outside the square X (plus and minus the square's half width.) Same goes for the other side as you need to make sure it doesn't reposition the player's X if the player's Y is outside the square Y (plus and minus the square's half height.)
- Methodic
These are both quality suggestions, thank you both so much!

I'll see if I can implement a good checking method taking these points into account. It's unfortunate that something so simple can turn out to be so complicated to resolve!

I'll post my findings, and if solved, my final code for anyone else having these problems too!
[font="Impact"]SOLVED![/font]

Ok, so the solution was a combination of things actually, but in the end I've found something that's now producing the perfect result - no more sticking, jittering or unsightly artefacts! And all this with pixel-perfect collision responses.

I finished with an implementation of the checking of X intersections first, then the Y. The Y checks would only run if the X checks failed.

Here's the final collision code:

N.B. This code assumes that the player's bounding box will be smaller than the bounding boxes of objects it needs to be stopped by (i.e. walls, floors etc).



function DoCollisions()
{
var newX = Player.x + speedX;
var newY = Player.y + speedY;
var runCheckY = true;
var runCheckX = true;

for (var i = 0; i < Level.numChildren; i++)
{
var clip = Level.getChildAt(i);

if (getQualifiedClassName(clip) == "BLOCK")
{
var objectBothAxes = new Rectangle(newX, newY, Player.width, Player.height);

if (WillIntersect(objectBothAxes, clip))
{
var objectOnlyMoveX = new Rectangle(newX, Player.y, Player.width, Player.height);

if (WillIntersect(objectOnlyMoveX, clip))
{
if (runCheckX)
{
// we've intersected on the X axis, so stop horizontal movement
if (speedX > 0)
{
// moving right, colliding with the LEFT side of the block
speedX = 0;
Player.x = clip.x - Player.width;
runCheckX = false;
}
else
{
// moving left, colliding with the RIGHT side of the block
speedX = 0;
Player.x = clip.x + clip.width;
runCheckX = false;
}
}
}
else
{
if (runCheckY)
{
var objectOnlyMoveY = new Rectangle(Player.x, newY, Player.width, Player.height);

if (WillIntersect(objectOnlyMoveY, clip))
{
// we've intersected on the Y axis, so stop vertical movement
if (speedY > 0)
{
// moving down, colliding with the TOP side of the block
speedY = 0;
Player.y = clip.y - Player.height;
runCheckY = false;
}
else
{
// moving up, colliding with the BOTTOM side of the block
speedY = 0;
Player.y = clip.y + clip.height;
runCheckY = false;
}
}
}
}
}
}
}
}



This code, however, strongly relies on important checks made to this function, which looks for the intersect:



function WillIntersect(object, clip)
{
if (object.x + object.width - 1 < clip.x || object.y + object.height - 1 < clip.y || object.x + 1 > clip.x + clip.width || object.y + 1 > clip.y + clip.height)
{
return false;
}
else
{
return true;
}
}



I discovered that the problem wasn't with zeroing out the speedX and speedY values, but with the code I was using the reposition the object in the collision response.

I found out that if I change the line of code Player.x = clip.x + clip.width (in response to moving left into the right side of a block) to the following code:

Player.x = clip.x + clip.width + 1

I wouldn't get any more 'sticking' or jumping artefacts. However, it left me with a 1 pixel gap between the collision surface and the player.

Solving it was simply a matter of changing the collision intersection threshold by 1 pixel, thus moving it from collision response to collision detection. You can see the 1 pixel threshold has been moved into the WillIntersect(...) method. This means, technically, that you're actually allow to invade the block by 1 pixel before the check returns a valid collision, whereas before you could still trigger it by being slightly outside, or on the edge.

Wow, that has been a week-and-a-half long problem now resolved.

I can't thank you both enough for giving me a fresh perspective on this, and I hope the information contained in my reply above will help others overcome this issue.
So I implemented the method that I stated above- and it's awesome!!

Here's my whole collision handling code:

int character::handle_collisions(){
event tmpvent;
tmpvent.caller=this;

if (tocol.size()>0){
int x=tocol.size();
}

sort(tocol.begin(),tocol.end(),ssorter(this));
for (int a = 0; a < tocol.size(); a++){
sprite* tmp=tocol[a];
if (tmp->type()==collisionType::impenetrable){
///Immovable, Impenetrable
if (collide(tmp,this)){
tmpvent.type="precollision";
tmp->handle_event(&tmpvent);
if (py + getsize().y <= tmp->getppos().y){ //bottom collided

setpos(x,tmp->getpos().y-getsize().y); //-1 at end?
setvel(xvel,tmp->getvel().y);
addpos(tmp->getvel().x,0); //Sticky Platforms
setcanjump(true);
bottomcollide();

}
if (py >= tmp->getppos().y + tmp->getsize().y){ //top collided

bool handlereg = true;

if (x+getsize().x-tmp->getpos().x<getsize().x*.2){
if (fabs(yvel)>getsize().y*.1){ ///VERY IMPORTANT to keep from catching on edges
setpos(tmp->getpos().x-getsize().x,y);
handlereg=false;
}
}
else if (tmp->getpos().x+tmp->getsize().x-x<getsize().x*.2){
if (fabs(yvel)>getsize().y*.1){
setpos(tmp->getpos().x+tmp->getsize().x,y);
handlereg=false;
}
}

if (handlereg){

setpos(x,tmp->getpos().y + tmp->getsize().y); //+1 at end?
setvel(xvel,tmp->getvel().y);
addpos(tmp->getvel().x,0); //Sticky Platforms
topcollide();

}
}
tmpvent.type="postcollision";
tmp->handle_event(&tmpvent);

}
if (collide(tmp,this)){
tmpvent.type="precollision";
tmp->handle_event(&tmpvent);
if (px + getsize().x <= tmp->getppos().x){ //right side collided


if (y+getsize().y-tmp->getpos().y<getsize().y*.25){
setpos(x,tmp->getpos().y-getsize().y);
}
else if (tmp->getpos().y+tmp->getsize().y-y<getsize().y*.25){
setpos(x,tmp->getpos().y+tmp->getsize().y);
}

else{
setpos(tmp->getpos().x-getsize().x,y); //-1 at end?
setvel(tmp->getvel().x,yvel);
rightcollide();

}
}
if (px >= tmp->getppos().x + tmp->getsize().x){ //left side collided


if (y+getsize().y-tmp->getpos().y<getsize().y*.25){
setpos(x,tmp->getpos().y-getsize().y);
}
else if (tmp->getpos().y+tmp->getsize().y-y<getsize().y*.25){
setpos(x,tmp->getpos().y+tmp->getsize().y);
}

else{
setpos(tmp->getpos().x + tmp->getsize().x,y); //+1 at end?
setvel(tmp->getvel().x,yvel);
leftcollide();
}
}
tmpvent.type="postcollision";
tmp->handle_event(&tmpvent);
}
}
else if (tmp->type()==collisionType::topcolonly){
///Can Only Collide With Top
if (collide(tmp,this)){
if ( py + getsize().y <= tmp->getppos().y){ //bottom collided
tmpvent.type="precollision";
tmp->handle_event(&tmpvent);
setpos(x,tmp->getpos().y-getsize().y); //-1 at end?
setvel(xvel,0);//tmp->getvel().y);
addpos(tmp->getvel().x,0); //Sticky Platforms
setcanjump(true);
bottomcollide();
tmpvent.type="postcollision";
tmp->handle_event(&tmpvent);
}
}
}
else if (tmp->type()==collisionType::independent){
///Object Handles Collision
tmpvent.type="handle_collision";

int ret = tmp->handle_event(&tmpvent);
if (ret==4){
return 4;
}
}

}
tocol.clear();
}


[s]This was meant for the other post o.O[/s]
How about, instead of repositioning anything, you check if the bounds of an object WILL intersect that of another if it moves. So you calculate the predicted position of your square, if that new position intersects , then do not allow a movement.

i.e.
position is (50,50)
each time you move, you move in an increment of 2 pixels
therefore, the predicted position will be the current posistion + move Increment.

if(predictedRectangle intersects/touches other rectangle)
Collision = true

if(Collision == false)
position += moveincrement

This topic is closed to new replies.

Advertisement