Saturday, December 22, 2018

physics - Collision detection logic


Edit 2


enter image description here



In the following picture sprite1 (the red square) is hitting the platform from the left, so:


 sprite1_rightEdge>platformSprite leftEdge

would be true, so I can simply reposition sprite1 to align it with the left edge of the platform. So....


sprite1X=platformSpriteX-platformSpriteWidth

This is fine, however, lets say that sprite1 hits from the right, the problem I have here is that the first condition (above) is still true. So....


sprite1_rightEdge>platformSprite leftEdge

So my sprite1 still gets positioned at the left edge of the platform......



The same also happens in reverse....


Using velocity


I've had some success with velocity, but again I'm not clear on how this works properly.


For example, if I say something like:


if (Sprite1 moving right at time of collision)

Then I can assume it's hit the platform from the left. This works, but what if the sprite is 'falling' from the top and lands on the platform while also moving right? It will again get re-positioned at the left edge of the platform.


partial sucess


So far, I managed to get this working (although not fully implemented yet), by saving the old position of Sprite1, repositioning it and checking the old position, so if the old position was to the left of the platform, then I can assume it came in from the left.


If someone could let me know the best way to proceed it would be much appreciated.



Just to clarify what I'm asking - I have 2 objects - 1 is a platform and the other is the main character, I wish to make the player stop regardless of which side he hits the platform (from the top, bottom, left or right) - I will definitely be moving at distances of more than 1 px at a time).


I've attempted using velocity but this doesn't seem to work correctly as well as double checking x/y positions once a collision has happened but again I get some very off results. Some pseudo code of how to detect which side a sprite has hit would be much appreciated :-)


I have researched this and have seen pretty much every Q&A on this site regarding this subject (as far as I know) but the answers are either not relevant to my particular query or I simply don't understand them.


Original question


I understand the basic principles of collision detection for squares / rectangles


i.e.,


(Pseudo code)


public boolean testCollision(){



if (bottom of sprite1return false;

if(top of sprite1>bottom of sprite2)
return false;

if(left edge of sprite1 > right edge of sprite2)
return false;

if(right edge of sprite1 < left edge of sprite2)

return false;

return true;
}

I also know that I can stop my sprite going off the edge of the screen like so:


if(spriteX<0){spriteX=0};

However using something akin to the first method above for detecting collision between 2 specific objects, how can I make my 1st sprite actually stop so he can't pass through it?


At the moment, although I can detect when the two are touching, I'm unsure how to make it so that they can't actually pass through each other.



A push in the right direction would be a great help - thanks!



Answer



Demonstration:


Crude but functional collision detection and response


Video: https://vimeo.com/64923588


The idea is that the player controlled sprite (actually a 32x32 pixels red box) can raise the speed of its next move, but it cannot go back to original speed except if it collide with something. Also if speed is enough the green wall can be "damaged" until it finally is destroyed. Gravity can be disabled. This situation shows AABB in action.


In the video, the red text shows some game variables, penx y peny are the lengths of penetration vectors into each axis (of the player sprite into other objects).


T.x and T.y are the coordinates of the 2d vector that go from the center of a box to the one again it is tested. If you go for AABB as I recommend, you will need T.


The formula used for T is:


T = PlatformAABB.position - PlayerAABB.position


Vector from Platform to Player.


If you do not want to use an structure/class to represent vectors, then another option is to use separate variables for x and y elements:


Tx = platform.x - player.x
Ty = platform.y - player.y

Note that the position of each AABB is measured from its center, not from top, left corner. So if you are positioning your sprites like me, using left and top to mean the upper left corner of the rectangle containing the sprite, then you may need to calculate the AABB center like this: PlayerAABB.position.x = player.left + player.width / 2. And the same for y but replacing left for top and width for height.


Tx = (platform.left + platform.width / 2) - (player.left + player.width / 2)
Ty = (platform.top + platform.height / 2) - (player.top + player.height / 2)


If your x and y coordinates are taken from the center of each sprite then don't worry simply apply the first formula.


To detect if boxes overlap


if (absolute(T.x) > (currentSpr.width / 2) + (otherSpr.width / 2)) stop here;
if (absolute(T.y) > (currentSpr.height / 2) + (otherSpr.height / 2)) stop here;

Is enough to know that T is greater in one axis than the sum of the length of the two relevant half extents of each involved box to know that there is no overlapping. Then you can stop and assume no collision.


Logic to detect collision finalizes here for most games. If your objects are moving really fast, more than its width or height in a single game tick, then you will need a more complex method, like divide the movement in small steps (less than the box dimensions) and then do the collision check. Remember the previous position is useful for this.


What to do when colliding?


For the case of Player Sprite vs Platform. Assuming Platforms never move, whatever impacts them.


If collision is positive, then you will have to find penetration vector to move the player sprite back. We are using AABB to make that task simple. You want the sprite to remain in touch with the platform, not to go back to its previous position, that may be far away.



We already calculated T, and used it to find overlapping, now we will use it again:


Formula for penetration vectors lengths. (We don't know direction yet).


penx = (currentSpr.width / 2) + (otherSpr.width / 2) - absolute(T.x);
peny = (currentSpr.height / 2) + (otherSpr.height / 2) - absolute(T.y);

Now, we choose the lesser of them to displace the sprite back. If you want to understand why the lesser, do the opposite, run your program and have some fun watching the result.


And we need a direction too, we know that we are always axis aligned but we still have 2 choices, left or right, up or down.


if (peny <= penx)
{
if (T[1] < 0) spr1.top += peny;

else
if (T[1] > 0) spr1.top -= peny;
} else
{
if (T[0] < 0) spr1.left += penx;
else
if (T[0] > 0) spr1.left -= penx;
}

(formula edited to make it more readable)



I have to do some extra calculations because of the left, top, system. I hope this does not make it to look more complicated than it really is.


What to do when colliding to another thing than a platform?


Be creative. You can divide the penetration vector by 2 and displace both sprites but in opposite directions. Or simply destroy the player. Or think in something more complex. Articles for "friction" in games exists.


Notes:


To return absolute values in C++ use fabs() function. In javascript use Math.abs(). Other languages have their owns.


I think that you don't need a vector library for this, as only basic operations were used.


Depending how you iterate through objects in your scene, the player sprite will not always be at the left (currentSpr in the examples) and the platforms may not always be at the right (otherSpr) in the calculations, beware of not applying the penetration vector to a platform by mistake.


Original answer:


Save the previous position of the sprite before move to the next position is what I'm doing for my game and I see no shame on that. I like the solution.


Now that you have previous position and current position, the vector giving you the direction of movement when colliding with other non movable objects (platforms, walls) has its tail in current position, and its head in previous position.



The question that remains to be answered is how much the colliding object should retreat. You don't want it to move back to previous position in all cases, specially when the speed of a given movement is too high. You probably want the object to remain in contact to the platform without trespassing its borders.


The SAT (Separating Axis Theorem) is what I would use in this case:


1 - Find penetration vector using SAT


2 - Move the penetrating sprite back by apply the penetration vector


Please, see this: http://www.metanetsoftware.com/technique/tutorialA.html


For this specific problem read sections, 0, 1 and 2.


Separating Axis Theorem for AABBs is what you need for this specific case.


Note the case where two character sprites collide is another story. You can use SAT too for detect penetration vector, but you probably want some kind of physics acting in both sprites, and alter the position of both. Depending on the game, maybe destroy the player sprite is enough, in this case no penetration vector needed.


No comments:

Post a Comment

Simple past, Present perfect Past perfect

Can you tell me which form of the following sentences is the correct one please? Imagine two friends discussing the gym... I was in a good s...