Topdown wall collision, not working

Started by
14 comments, last by BurdenJohn 9 years, 10 months ago

Hey BurdenJohn,

If I may suggest, it might be helpful to rethink your approach a little bit. Here is an approach that worked well for me:

- do location updates and collision checks one axis at a time for each update (it doesn't really matter which one you do first)

- i.e., update the x location given current velocity, check for collisions, respond; repeat for y axis

- instead thinking about how to keep the player rectangle from intersecting with objects (which seems to be the approach you're taking, if I understand it correctly), just let the objects intersect and then correct appropriately

- for example, if the player "runs into a wall", what happens is that the player intersects with the wall, and then is pushed out to react to collision; this is not the only way model your collision detection/response, but I find that it's easy to reason about

I have some sample code implemented in Java which does this kind of basic 2d rectangular collision detection/response. The movement simulation, collision checks, and collision responses are modeled here:

https://github.com/larsbutler/gamedemo/blob/opengl/src/com/larsbutler/gamedemo/core/GameState.java#L95

The collision calculations are implemented here:

https://github.com/larsbutler/gamedemo/blob/opengl/src/com/larsbutler/gamedemo/math/Collision.java

I also have implemented some basic collision test cases:

https://github.com/larsbutler/gamedemo/blob/opengl/tests/com/larsbutler/gamedemo/math/CollisionTest.java

These even account for cases where an object is moving so fast that it "passes through" another object.

Hope that helps!

Advertisement

A lot of your problems seem to come from bundling together all of your checks and causing conflicts. Try separating your functions based on what direction your going and what collisions can happen in that direction to avoid more confusion. For example if your moving up you only need to check for player top collisions. You don't need to check whether or not you've collided with the bottom, left, or right walls.

A lot of your problems seem to come from bundling together all of your checks and causing conflicts. Try separating your functions based on what direction your going and what collisions can happen in that direction to avoid more confusion. For example if your moving up you only need to check for player top collisions. You don't need to check whether or not you've collided with the bottom, left, or right walls.

Well now I'm trying it your way as I'm more comfortable with this method, can't be looking through tons of code right now to solve this simple task.

Unfortunately, I'm still getting stuck along the edges, even when I check against what directions I'm going.

Here's a picture showing what's happening now for the following cases I'm testing (I only test two cases for now since there's no point in going forward if my player is all ready getting stuck):
qD242Lp.png

Currently right now I have the following cases:
- Player is going up but NOT left or right (only going up)

- Player is going up AND left, but not right (up and left)

Even with just two cases so far, the player is all ready getting stuck..

If my player is going up and left, and he's colliding against the right side of the block, its smooth! Perfect, right?

If my player is going up, I'm only checking collision against the bottom of the block, it works! Perfect, right?

Well if my player is going up and left and he's colliding against the bottom of the blocks, as he glides to the left
across the bottom of the blocks, he gets stuck on the edges between the two blocks. (x = brect.right + 1)

This is bad, and it's the exact same thing that was happening before.

Here are my collision functions:


//player right collides with box left
bool SFMLPlayer::check_pright_bleft(RECTANGLE* _prect, RECTANGLE* _brect, int move_speed){
	RECTANGLE prect = *_prect;
	RECTANGLE brect = *_brect;
	int offset = move_speed;

	if(prect.right > brect.left && prect.right <= brect.left + offset){
		if ( (prect.top >= brect.top && prect.top < brect.bottom) ){
			return true;
		}
		else if ( (prect.bottom >= brect.top && prect.bottom < brect.bottom) ){
			return true;
		}
	}

	return false;
}
//player left collides with box right
bool SFMLPlayer::check_pleft_bright(RECTANGLE* _prect, RECTANGLE* _brect, int move_speed){
	RECTANGLE prect = *_prect;
	RECTANGLE brect = *_brect;
	int offset = move_speed;

	if(prect.left < brect.right + offset && prect.left > brect.right - offset){
		if ( (prect.top >= brect.top && prect.top < brect.bottom) ){
			return true;
		}
		else if ( (prect.bottom >= brect.top && prect.bottom < brect.bottom) ){
			return true;
		}
	}
	return false;
}
//player bottom collides with box top
bool SFMLPlayer::check_pbottom_btop(RECTANGLE* _prect, RECTANGLE* _brect, int move_speed){
	RECTANGLE prect = *_prect;
	RECTANGLE brect = *_brect;
	int offset = move_speed;

	if(prect.bottom > brect.top && prect.bottom <= brect.top + offset){
		if( (prect.right >= brect.left && prect.right < brect.right) ){
			return true;
		}
		else if( (prect.left >= brect.left && prect.left < brect.right) ){
			return true;
		}
	}

	return false;
}
//player top collides with box bottom
bool SFMLPlayer::check_ptop_bbottom(RECTANGLE* _prect, RECTANGLE* _brect, int move_speed){
	RECTANGLE prect = *_prect;
	RECTANGLE brect = *_brect;
	int offset = move_speed;

	if(prect.top < brect.bottom && prect.top >= brect.bottom - offset){
		if( (prect.right >= brect.left && prect.right < brect.right) ){
			return true;
		}
		else if( (prect.left >= brect.left && prect.left < brect.right) ){
			return true;
		}
	}

	return false;
}

And here is where I'm using the test cases, inside the block iteration (looping through all blocks in the vector):


    for(std::vector<Block>::iterator block = blocks->begin(); block != blocks->end(); ++block) {
      RECTANGLE prect;
      prect.left = this->x;
      prect.top = this->y;
      prect.right = this->x + this->size;
      prect.bottom = this->y + this->size;

      RECTANGLE brect;
      brect.left = block->x;
      brect.top = block->y;
      brect.right = block->x + block->size;
      brect.bottom = block->y + block->size;
    
      //AABB collision approach
      int offset = move_speed;

      //We are ONLY going up, check the top of player against bottom of block
      if(up && !right && !left){
        if( check_ptop_bbottom(&prect, &brect, move_speed) ){
          y = brect.bottom+1;
        }
      }

      //We are going UP AND LEFT, check the left side of player against right side of block,
      //AND also check the top of player against bottom of block (we could be on the right side or bottom side of the block)
      else if(up && (left && !right)){
        if( check_pleft_bright(&prect, &brect, move_speed) ){
          x = brect.right + 1;
        }
        else if( check_ptop_bbottom(&prect, &brect, move_speed) && prect.top > brect.top ){
          y = brect.bottom;
        }

      }
      
    }

Does anyone have any insight as to why my player gets stuck while gliding to the left along the bottom of the blocks,

yet he does not get stuck while gliding up along the right side of the blocks?

Where in my collision code, or even my test case code, is it allowing the player to stop moving to the left when he's on the bottom of the blocks and pushing up?

I've spent way too long on this and its unfortunately blocking me from doing any other work.

I've tried everything from changing an "<=" to an "<", placing offsets in different places, I just can't figure out how to make it so I can smoothly

glide across all sides. There's something wrong in my collision code and its incredibly hard to detect.

Looks to me that your block seems to stop moving when the right hand sides align more than the left hits the middle

Im not sure but i think when StaticPoof said


Try separating your functions based on what direction your going and what collisions can happen in that direction to avoid more confusion. For example if your moving up you only need to check for player top collisions. You don't need to check whether or not you've collided with the bottom, left, or right walls.

ment that you should be doing more like this


if(up)
{
    if(check_ptop_bbottom(&prect, &brect, move_speed))
        y = brect.bottom - 1;
}
else if(down)
{
    if(check_pbottom_btop(&prect, &brect, move_speed))
        y = brect.top + 1;
}

if(left)
{
    if(check_pleft_bright(&prect, &brect, move_speed)
        x = brect.right + 1;
}
else if(right)
{
    if(check_pright_bleft(&prect, &brect, move_speed)
        x = bright.left - 1;
}

from what i can work out is prect is your player and brect is your block


if(prect.left < brect.right + offset && prect.left > brect.right - offset)

why are you using your offset twice id be more inclined if checking player left is gonna hit player right if your using offset so moving left is offset = -1


if(prect.bottom + offset_y >= brect.top || prect.top + offset_y <= brect.bottom)
    return false; // hasnt hit as it is above/below
else if(prect.left >= bright.right && prect.left + offset_x < bright.right)
    return true;  //has hit
else
    return false;

May this can be useful (it has a nice explanation):

Perhaps you don't need the Intersection Time (if is your first try to write collision code).

http://www.gamedev.net/page/resources/_/technical/game-programming/swept-aabb-collision-detection-and-response-r3084

Got it working!


  //Simple rectangle intersection function
  bool intersect(RECTANGLE a, RECTANGLE b)
  {
    if (a.left < b.right && b.left < a.right && a.top < b.bottom)
      return b.top < a.bottom;
    else
      return false;
  }

  //Loops through all solids to see if two rectangles (given offset)
  //are intersecting.
  bool Player::place_meeting(int offsetx, int offsety, RECTANGLE prect){
    RECTANGLE pNew;
    pNew = prect;
    pNew.left+=offsetx;
    pNew.right+=offsetx;
    pNew.top+=offsety;
    pNew.bottom+=offsety;

    bool inter = false;
    for(std::vector<Block>::iterator block = blocks->begin(); block != blocks->end(); ++block) {
      RECTANGLE brect;
      brect.left = block->x;
      brect.top = block->y;
      brect.right = block->x + block->size;
      brect.bottom = block->y + block->size;

      inter = intersect(pNew, brect);
      if(inter) break;

    }

    return inter;

  }

  //Player update (60fps)
  //left, right, up, and down are determined by keypress/keyrelease events
  void Player::update(){
    int spd = 2;
    
    RECTANGLE prect;
    prect.left = this->x;
    prect.top = this->y;
    prect.right = this->x + this->size;
    prect.bottom = this->y + this->size;

    //Detect if we haven't hit anything (or in this case, are going to
    //because speed is a factor.  
    //If we aren't going to hit anything, let the player keep moving
    //in that direction
    if (left)  {if (!(place_meeting(-spd,0, prect))) {x -= spd;}}
    if (right)  {if (!(place_meeting(spd,0,prect))) {x += spd;}}
    if (up)  {if (!(place_meeting(0,0-spd,prect))) {y -= spd;}}
    if (down)  {if (!(place_meeting(0,0+spd,prect))) {y += spd;}} 
    
  }

I detect for the collision using my version of a Game Maker function called: place_meeting().

It simply detects (with an offset), if my players rectangle is going to intersect with a blocks rectangle, given my speed

as an offset. And if it is, I stop letting my player move in that direction.

Works like a charm.

This topic is closed to new replies.

Advertisement