Allegro.cc - Online Community

Allegro.cc Forums » Programming Questions » Solid Collision "Sliding" (Man, I'm bad at explaining things)

This thread is locked; no one can reply to it. rss feed Print
Solid Collision "Sliding" (Man, I'm bad at explaining things)
Eric Johnson
Member #14,841
January 2013
avatar

Hello. I've been working towards achieving an effect in which, should a player hit a solid object, they can slide passed it, despite them pressing more than one arrow key. For example, say you're going up to the right and you hit a solid object from the bottom; well, stop moving up and begin just sliding to the right instead. A lot of top-down video games have this functionality, but I am having difficulties achieving it.

I have two integers: dirX and dirY; each of which corresponds to the player's X and Y positions (dirX = 1 is right; dirX = -1 is left; dirX = 0 is no x movement; dirY = 1 is down; dirY = -1 is up; dirY = 0 is no Y movement). Here's my current code for what to do upon hitting a solid object:

#SelectExpand
1void Player::hitSolid() { 2 3 if (dirX == -1) { 4 5 x += speed; 6 } 7 else if (dirX == 1) { 8 9 x -= speed; 10 } 11 12 if (dirY == 1 && dirX == 0) { 13 14 y -= speed; 15 } 16 else if (dirY == -1 && dirX == 0) { 17 18 y += speed; 19 } 20 21}

The main issue with this is that it will not allow you to slide around objects as mentioned above. Instead, if you collide, it locks up until you release the key that is holding you back. I did find a partial solution however...

#SelectExpand
1void Player::hitSolid() { 2 3 if (dirY == 1 && dirX == 0) { 4 5 y -= speed; 6 } 7 else if (dirY == -1 && dirX == 0) { 8 9 y += speed; 10 } 11 12 if (dirX == -1) { 13 14 x += speed; 15 } 16 else if (dirX == 1) { 17 18 x -= speed; 19 } 20 21}

This would allow me to slide on the left and right sides of the collided object (the function is called after confirming a collision of a specific tile, mind you). I haven't yet had any luck getting it to allow the player to slide on the top and bottom sides of the object though. Do you have any input? Thank you.

PS: This is for a top-down game, so there is no velocity involved.

OnlineCop
Member #7,919
October 2006
avatar

I see no difference between those examples; they are the same equations, simply reordered.

Just for clarification, is this what you're seeing, and trying to remedy?

So if I have this object:

    xxxxx
    xxxxx
    xxxxx

and my colliding object is calculated to be here:

    xxxxx
    xxxxx
    xxxxx

    0

then I first try to move up:

    xxxxx
    xxxxx
    xxxxx
    0      <-- success

Next, I try to move right:

    xxxxx
    xxxxx
    xxxxx
     0     <-- success

Now I'm on my next update loop. I am at this:

    xxxxx
    xxxxx
    xxxxx
     0

and try to move up. Since I can't, I stay there:

    xxxxx
    xxxxx
    xxxxx
     0     <-- failed, still in this position

Then I try to move right:

    xxxxx
    xxxxx
    xxxxx
      0    <-- success

If I repeat those last 2 moves, I am now here:

    xxxxx
    xxxxx
    xxxxx
        0  <-- here now

What you're probably seeing is that on the next round, where you would expect my character to be able to move from:

    xxxxx
    xxxxx
    xxxxx
        0  <-- here

to

    xxxxx
    xxxxx
    xxxxx0 <-- here

is that my character cannot move up, but it can move right:

    xxxxx
    xxxxx
    xxxxx
         0 <-- here

and it's not until the next update that I can start moving diagonally again:

    xxxxx
    xxxxx
    xxxxx 0 <-- probably one space further right than you anticipated

Is that what you're seeing and trying to fix, or something else?

Jeff Bernard
Member #6,698
December 2005
avatar

I'm a little confused by the two code blocks you posted, since they're functionally the same (but apparently only the second one is supposed to work?). Originally I thought you were talking about rolling around corners (ie- holding left, player hits obstacle, automatically moves one tile up without having to press up, continues left past obstacle), but I'm thinking now you're talking about not "undoing" both horizontal and vertical movement when hitting an object, only undoing the one that caused the collision.

The reason you aren't sliding from the top or bottom is because you are always undoing your horizontal movement, no matter what, when you call hitSolid. Basically, instead of undoing all movements, you need to figure out whether you hit the obstacle horizontally or vertically and only undo the appropriate movement. Then you can hold up+left, hit something from the bottom, continue moving left but don't continue up until you're past the obstacle.

Ultimately your code should be something like:

void Player::hitSolid() {
   ///TODO figure out what side the hit was

   if (CollisionFromTop || CollisionFromBottom) {
      y += -dirY*speed; // undo vert movement
   }
   // if you want to block corner collisions rather than just going a direction,
   // these aren't mutually exclusive
   else {
      x += -dirX*speed; // undo horiz movement
   }
}

As for figuring out what side the collision was, it's trivial if you were only moving in one direction. Otherwise (if the speed is less than the player/obstacle's sizes), you can measure the differences of each object's corresponding edges (ie- player's left side to obstacles right side, player's top to obstacle's bottom), and the minimum magnitude difference is where the collision occurred. If a horizontal and vertical difference tie for minimum, then you hit a corner and you can either undo both movements, or just pick one direction to dominate the other.

I don't know how you're doing collision detection, but you may run into issues trying to slide across multiple adjacent tiles, getting false corner collisions...

--
I thought I was wrong once, but I was mistaken.

Eric Johnson
Member #14,841
January 2013
avatar

OnlineCop, I'm trying to do this: once you hit a solid object, you won't be able to go any further in the direction that you hit the object from. So if you're going up to the right and hit the bottom of an object, prevent the player from going up any further, but allow them to move to the left or the right, because nothing is blocking their path there.

Jeff Bernard, your code works well for being able to slide around the top and bottom of object, but I'm still having difficulties with the left and right sides (it allows me to pass through them if I am holding a vertical arrow key as well as a horizontal one). I was having this issue before, where I could only achieve the effect on two of the four sides, but not all of them. Also, I am using bounding box collision.

I appreciate the responses guys. :)

Jeff Bernard
Member #6,698
December 2005
avatar

The only other suggestion I would have it to, instead of merely undoing the last movement, actually move the player to next to the obstacle.

That's what I do in my game, using the method of determining the side of a collision I described earlier and it works fine. For the interested here's the code (like yours, this function is only called once a collision, ie- overlapping bounding boxes, is detected). In this code I ignore collisions between mobile sprites (ie- if two objects can both move, they can move through each other), and only care about collisions between sprites that can and can't move (ie- a moving sprite can't move through a stationary wall), so objI would correspond to your obstacle and objJ would correspond to your player:

#SelectExpand
1void CollisionHandler::check(lp::Object* objI, lp::Object* objJ) 2{ 3 // works well enough for simple games with few collidables 4 ///TODO profile with many collidable objects 5 if (!objI->isMobile() && objJ->isMobile()) // only handle valid collisions 6 { 7 int edge = FROM_TOP; 8 9 float xi = objI->getPosition().getX()+myl::Sprite::PADDING; 10 float yi = objI->getPosition().getY()+myl::Sprite::PADDING; 11 float xj = objJ->getPosition().getX()+myl::Sprite::PADDING; 12 float yj = objJ->getPosition().getY()+myl::Sprite::PADDING; 13 14 int wi = objI->getWidth()-myl::Sprite::PADDING*2; 15 int hi = objI->getHeight()-myl::Sprite::PADDING*2; 16 int wj = objJ->getWidth()-myl::Sprite::PADDING*2; 17 int hj = objJ->getHeight()-myl::Sprite::PADDING*2; 18 19 float left = xj+wj-xi; 20 float right = xi+wi-xj; 21 float top = yj+hj-yi; 22 float bottom = yi+hi-yj; 23 24 bool topOverBottom = top < bottom; 25 bool rightOverLeft = right < left; 26 if (topOverBottom) 27 { 28 if (rightOverLeft) 29 { 30 if (top < right) 31 { 32 edge = FROM_TOP; 33 } 34 else 35 { 36 edge = FROM_RIGHT; 37 } 38 } 39 else 40 { 41 if (top < left) 42 { 43 edge = FROM_TOP; 44 } 45 else 46 { 47 edge = FROM_LEFT; 48 } 49 } 50 } 51 else 52 { 53 if (rightOverLeft) 54 { 55 if (bottom < right) 56 { 57 edge = FROM_BOTTOM; 58 } 59 else 60 { 61 edge = FROM_RIGHT; 62 } 63 } 64 else 65 { 66 if (bottom < left) 67 { 68 edge = FROM_BOTTOM; 69 } 70 else 71 { 72 edge = FROM_LEFT; 73 } 74 } 75 } 76 77 // prevent actually passing through things 78 // and use velocity to handle corner collisions 79 ///TODO don't create objects in game loop when you don't have to 80 switch (edge) 81 { 82 case FROM_TOP: 83 if (objJ->getVelocity().getY() > 0) 84 { 85 objJ->move(lp::Vector(0, yi-yj-hj-myl::Sprite::PADDING-1)); 86 } 87 break; 88 case FROM_BOTTOM: 89 if (objJ->getVelocity().getY() < 0) 90 { 91 objJ->move(lp::Vector(0, yi+hi-yj+1)); 92 } 93 break; 94 case FROM_LEFT: 95 if (objJ->getVelocity().getX() > 0) 96 { 97 objJ->move(lp::Vector(xi-xj-wj-myl::Sprite::PADDING-1, 0)); 98 } 99 break; 100 case FROM_RIGHT: 101 if (objJ->getVelocity().getX() < 0) 102 { 103 objJ->move(lp::Vector(xi+wi-xj+1, 0)); 104 } 105 break; 106 } 107 } 108}

(Some clarification, my code assumes objects have bounding boxes, but my physics engine has no notion of bounding boxes, per se, rather bounding shapes are a collection of vertices and the separating axis thereon is used to determine collisions, so that's why I don't get the object's bounding box directly (you can't), instead I calculate it on the fly based on the conventions of my current game.)

--
I thought I was wrong once, but I was mistaken.

Edgar Reynaldo
Major Reynaldo
May 2007
avatar

You have to do intercept times and find out which edge collided first.
d = r*t (distance = rate * time)
t = d/r

1. Find relative velocity of one rectangle to another (leader, and collider).
2. Find leading edges of leader, and colliding edges of collider.
3. Find x-intercept times
4. Find y ranges of edges in 2 at times in 3.
5. If range overlaps, x-collision at that time.
6-8. Repeats steps for y-intercept times, x ranges, and y collision.
9. Find earliest positive, and possibly simultaneous, collision time(s), disregarding negative collision times as they happened in the past.
10. Advance time to that point, ceasing velocity in the direction of the collision, and placing the objects right next to each other along that edge. It will temporarily slow down the object but it should slide after that, like down a wall.

I won't write the code out for you, but this basic premise works. Also, you should only perform 10) after you have checked against each possible colliding object. Because the collision should stop after the earliest one.

Schyfis
Member #9,752
May 2008
avatar

Assuming you're working with axis-aligned (non-rotated) rectangles, the solution is easy.

Pseudocode:

#SelectExpand
1resolveCollision(object1, object2) { 2 if (object1.getRightEdge() < object2.getLeftEdge() || object1.getLeftEdge() > object2.getRightEdge() || object1.getTopEdge() > object2.getBottomEdge() || object1.getBottomEdge() < object2.getTopEdge()) return; //objects are not colliding, so do nothing 3 4 dx = object2.x - object1.x 5 dy = object2.y - object1.y 6 7 if (object1.can_move && !object2.can_move) { 8 //push object1 out of object2 in the distance of least penetration. 9 if (dx < dy) object1.x += dx 10 else object1.y += dy 11 } 12 13 if (!object1.can_move && object2.can_move) { 14 //push object2 out of object1 in the distance of least penetration. 15 if (dx < dy) object2.x -= dx 16 else object2.y -= dy 17 } 18 19 if (object1.can_move && object2.can_move) { 20 //push both objects out of each other in the distance of least penetration. 21 if (dx < dy) { 22 object1.x += dx / 2 23 object2.x -= dx / 2 24 } else { 25 object1.x += dx / 2 26 object2.y -= dy / 2 27 } 28 } 29}

Disclaimer: I'm really tired, so I might have mixed the signs up or made some other typo. But the idea is there. It works because the rectangles can't possibly be colliding if one of those edges isn't past its opposite partner. So by ruling all those cases out, what's left is only rectangles that collide.

How you implement getLeftEdge, getRightEdge, getTopEdge, and getBottomEdge is up to you, but presumably it would be something like:

float Object::getLeftEdge() {
  return x - sprite_width / 2;
}

//similar functions for other edges

In the end, code like this should allow "sliding" along walls.

________________________________________________________________________________________________________
[freedwill.us]
[unTied Games]

Go to: