Sign in to follow this  
PixelPrime11

2D Platform edge collisions

Recommended Posts

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:

[code]Player.x += speedX;
Player.y += speedY;[/code]

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:

[code]
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;
}
}[/code]

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:

[list][*]If no intersection was detected with any object, the [b]speedX [/b]and [b]speedY [/b]values will remain unmodified.[*]If the player falls onto the top of a block, the [b]speedY [/b]value is zeroed-out and they are [b]repositioned[/b] on top of the block with pixel precision[*]If the player hits the underside of a block, the [b]speedY[/b] value is zeroed-out and they are [b]repositioned[/b] directly underneath the block[*]If the player hits the left, or right side of a block, the [b]speedX[/b] value is zeroed-out and they are [b]repositioned[/b] to the relevant edge.[/list]
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.

[b]Video of the effect in question:
[/b][url="http://www.youtube.com/watch?v=nFusvi8U_-A"]http://www.youtube.c...h?v=nFusvi8U_-A[/url]

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!

[CODE]
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 { }
}
}
}
}
[/CODE]

Thanks to anyone who can offer some advice here.

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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.)

Share this post


Link to post
Share on other sites
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!

Share this post


Link to post
Share on other sites
[font="Impact"][size="3"][b]SOLVED![/b][/size][/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:

[b]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)[/b].

[CODE]

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;
}
}
}
}
}
}
}
}

[/CODE]

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

[CODE]

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;
}
}

[/CODE]

I discovered that the problem wasn't with zeroing out the [b]speedX[/b] and [b]speedY[/b] 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 [b]Player.x = clip.x + clip.width[/b] (in response to moving left into the right side of a block) to the following code:

[b]Player.x = clip.x + clip.width + 1

[/b]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 [b]WillIntersect(...) [/b]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.

Share this post


Link to post
Share on other sites
So I implemented the method that I stated above- and it's awesome!!

Here's my whole collision handling code:

[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();
}[/code]

[s]This was meant for the other post o.O[/s]

Share this post


Link to post
Share on other sites
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

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this