Sign in to follow this  
Septagon

Glitchy collision detection in platformer (solved)

Recommended Posts

This is about player vs. block collisions in a tile based 2d platformer. Currently it's using a 'shortest offset' method. If there's any overlap between the player and a block, the block assumes the player has collided with the closest edge, and then nudges the player in that direction, eliminating the overlap. The problem is that the collision often misfires. If, for instance, you are walking along a horizontal row of blocks, the blocks will sometimes decide you are colliding with their sides instead of their tops. Ick! Has anyone worked out a really solid alternate approach? For reference player velocities approach 1/2 tile per frame at times. [Edited by - Septagon on August 20, 2008 4:08:20 PM]

Share this post


Link to post
Share on other sites
Hi Septagon,
Perhaps the site below could help you. It is using Flash code, but I was able to get something working for a 2d platform game I had started making using Delphi (Object Pascal).

http://www.tonypa.pri.ee/tbw/tut12.html

Especially, see the "Hit the wall" and "Jumping" links on the left.

I hope this helps :-)

cheers,
Paul

Share this post


Link to post
Share on other sites
To make it a bit clearer, I have pasted the code my player sprite uses to do collisions with the surrounding tiles, etc. so you can get a better idea.


Procedure TSprite_Player.DoUpdate(Const ALevel : TObject;
Const ATimeSlice : Single);
Var
xtile : Integer;
ytile : Integer;
downY : Integer;
upY : Integer;
leftX : Integer;
rightX : Integer;
upleft : Boolean;
downleft : Boolean;
upright : Boolean;
downright : Boolean;
left : Boolean;
right : Boolean;
ldx,rdx : Integer;
tdy,bdy : Integer;
i : Integer;
Platform : TSprite_MovingPlatform;
MoveX : Boolean;
MoveY : Boolean;
Begin
xtile := Math.floor(x / TLevel(ALevel).TileWidth);
ytile := Math.floor(y / TLevel(ALevel).TileHeight);
MoveX := False;
MoveY := False;
FTileSet.ImageList.GetImageCollisionDeltasByIndex(Frame,ldx,rdx,tdy,bdy);
// check against static solid tiles
GetCorners(ALevel,x,y + vely,downY,upY,leftX,rightX,upleft,downleft,upright,downright,left,right);
If vely > 0 Then
Begin
If upleft And upright Then
Begin
MoveY := True
End
Else
Begin
y := (ytile + 1) * TLevel(ALevel).TileHeight - (height/2 - tdy) - 0.1;
vely := 0;
DoPlayerTileCollision(ALevel,Self,leftX,upY);
DoPlayerTileCollision(ALevel,Self,rightX,upY);
End;
End
Else
If vely < 0 Then
Begin
If Not Climbing And TileIsClimable(ALevel,Layer,xtile,downY) And
Not TileIsClimable(ALevel,Layer,xtile,upY) Then
Begin
y := ytile * TLevel(ALevel).TileHeight + (height/2 - bdy) + 0.1;
vely := 0;
If Jumping Then Jumping := False;
End
Else
If downleft And downright Then
MoveY := True
Else
Begin
y := ytile * TLevel(ALevel).TileHeight + (height/2 - bdy) + 0.1;
vely := 0;
If Jumping Then Jumping := False;
DoPlayerTileCollision(ALevel,Self,leftX,downY);
DoPlayerTileCollision(ALevel,Self,rightX,downY);
End;
End;
GetCorners(ALevel,x + velx,y,downY,upY,leftX,rightX,upleft,downleft,upright,downright,left,right);
If velx > 0 Then
Begin
If upright And downright And right Then
MoveX := True
Else
Begin
x := (xtile + 1) * TLevel(ALevel).TileWidth - (width/2 - rdx) - 0.1;
DoPlayerTileCollision(ALevel,Self,rightX,upY);
DoPlayerTileCollision(ALevel,Self,rightX,(upY + downY) Div 2);
DoPlayerTileCollision(ALevel,Self,rightX,downY);
End;
End
Else
If velx < 0 Then
Begin
If upleft And downleft And left Then
MoveX := True
Else
Begin
x := xtile * TLevel(ALevel).TileWidth + (width/2 - ldx);
DoPlayerTileCollision(ALevel,Self,leftX,upY);
DoPlayerTileCollision(ALevel,Self,leftX,(upY + downY) Div 2);
DoPlayerTileCollision(ALevel,Self,leftX,downY);
End;
End;
// check against moving platforms
If TLevel(ALevel).CollidedWithMovingPlatforms(Self,HitPlatforms) Then
Begin
For i := 0 To HitPlatforms.Count - 1 Do
Begin
Platform := TSprite_MovingPlatform(HitPlatforms.Items[i]);
If (vely < 0) And (y > ((Platform.y - Platform.height/2) + Platform.height + (height/2 - bdy) + Platform.vely - 5)) Then
Begin
y := (Platform.y - Platform.height/2) + Platform.height + (height/2 - bdy) + Platform.vely;
vely := 0;
Jumping := False;
End;
End;
End;
If MoveY Then y := y + vely;
If MoveX Then x := x + velx;
If UseOtherFriction Then
velx := velx * OtherFriction
Else
Begin
If Running Then
velx := velx * RunFriction
Else
velx := velx * WalkFriction;
End;

If Climbing Then
vely := 0
Else
vely := vely + Gravity * ATimeSlice;
If vely < -TLevel(ALevel).TileHeight / 2 Then
vely := -TLevel(ALevel).TileHeight / 2
Else
If vely > TLevel(ALevel).TileHeight / 2 Then
vely := TLevel(ALevel).TileHeight / 2;
End;


cheers,
Paul

Share this post


Link to post
Share on other sites
OK, problem solved! We didn't use the 'four corners' approach, but Paul's solution was helpful nonetheless.

The approach used combines two checks:
(1) smallest offset: are we overlapping with the block, and which side are we closest to?
(2) Check the result against the block's neighbors. Disable collision checks for sides that aren't exposed (because they face neighboring blocks).

Here's the code in case anyone else runs into the same issue (ActionScript 3):

var blocked:Array = new Array(4);
blocked = main.GetBlocking(blockX, blockY);
sideHit = -1;
smallestOffset = 1;

// Left
tempOverlap = right1 - left2;
// trace("Left overlap: " + tempOverlap);
if (tempOverlap <= 0)
colliding = false;
else if (tempOverlap > 0)
{
if (tempOverlap < smallestOffset)
{
if (blocked[2] == false)
{
sideHit = 2;
smallestOffset = tempOverlap;
correctedX = left2 - characterSizeX;
correctedY = characterY;
}
}
}

// Right
tempOverlap = right2 - left1;
// trace("Right overlap: " + tempOverlap);
if (tempOverlap <= 0)
colliding = false;
else if (tempOverlap > 0)
{
if (tempOverlap < smallestOffset)
{
if (blocked[0] == false)
{
sideHit = 0;
smallestOffset = tempOverlap;
correctedX = right2 + characterSizeX;
correctedY = characterY;
}
}
}

// Top
tempOverlap = bottom1 - top2;
// trace("Top overlap: " + tempOverlap);
if (tempOverlap <= 0)
colliding = false;
else if (tempOverlap > 0)
{
if (tempOverlap < smallestOffset)
{
if (blocked[1] == false)
{
sideHit = 1;
smallestOffset = tempOverlap;
correctedX = characterX;
correctedY = top2 - characterSizeY;
}
}
}

// Bottom
tempOverlap = bottom2 - top1;
// trace("Bottom overlap: " + tempOverlap);
if (tempOverlap <= 0)
colliding = false;
else if (tempOverlap > 0)
{
if (tempOverlap < smallestOffset)
{
if (blocked[3] == false)
{
sideHit = 3;
smallestOffset = tempOverlap;
correctedX = characterX;
correctedY = bottom2 + characterSizeY;
}
}
}
/* Note:
We're going to need a special case for blocks that have no
open sides (surrounded by four blocks). */
if (sideHit == -1)
{
colliding = false; // Does that do it?
}

/* If 'colliding' is still true, our boxes are overlapping.
We can use the offset to rectify this. */
if (colliding)
{
if (main.IsTop(sideHit))
{
vY = 0;
character.SetFreefall(false);
}
else if (main.IsSide(sideHit))
{
// Don't assume we're hitting sides when we clearly aren't!
vX = 0;
}
else if (main.IsBottom(sideHit))
vY = 0;
characterX = correctedX;
characterY = correctedY;
character.SetPositionX(characterX);
character.SetPositionY(characterY);
character.SetSpeedX(vX);
character.SetSpeedY(vY);
}

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