Translate

Saturday, March 15, 2014

Collision Tutorial

This tutorial is long overdue so I decided to finally make it. Today I'm going to teach you all how I made my collision system. The only thing it is missing is quad trees or BSP trees, which I will fiddle around with eventually.

I would like to say this is not the best collision system in the world, and I bet there are better ways to do rectangle collision, but this is how I did it.

The most common approach to rectangle collision is that if a collision occurs it will move the object back to where it initially was. In my opinion, this is a bad and sloppy method to go about because you get this:

Do you see the problem here? The problem is the block is pixels above the ground which looks, well bad. So what should we do?





First off we are going to calculate collisions differently. When we collide with the wall, floor, etc. rather than moving the player back to where they previously were, we are going to calculate how far to move them back. Note: I am going to be using C++ and SDL for the programming.

We need to be able to check if there's a collision before we fix it.

Code:
1    bool CheckCollision(SDL_Rect A, SDL_Rect B)
2    {
3        if(A.y+A.h<=B.y or A.y>=B.y+B.h or A.x+A.w<=B.x or A.x>=B.x+B.w)
4        {
5            return false;
6        }
7        return true;
8    }

So now we can check collisions. What we now want to do is actually fix the collisions.
Since we are using SDL_Rects we have 4 values given to us, the x, y, w(width), h(height). We need to get this point(the corner):
Lets just call the point "player.c"
To do this we add the x+w. Now you may be thinking, why do we need this? We use it to be able to identify how far to push the player back when hitting a wall to the right of it.
For this example lets just say that:
player.x=10                                    Wall.x=16
player.y=0                                      Wall.y=27
player.w=16                                   Wall.w=8
player.h=16                                    Wall.h=52

Note: This example will be used multiple times.
.
So we subtract player.c to wall.x from player.x, or (10+16)-16=10. We know we need to move the player 10 pixels to the left. 
Now lets generalize it to programming terms:

Code:
1    player.x-=player.c-wall.x;

And revise it to do all of that at once:

Code:
1    player.x-=(player.x+player.w)-wall.x;

Now if you implement this code you may notice that it only works correctly when colliding with a wall left of the player. This is because the code says to move the player to the left of the wall whenever colliding with a wall. To fix this we need to add support to the player when colliding to a wall left of it. To do this we reverse it. Rather than adding player.x and player.w and subtracting from wall.x we add wall.x. Also instead of subtracting the player.x, you add the player.x.

Example: (using the original example)
(16+8)-10=14, So we need to move the player 14 pixels to the right.


Code:
1    player.x+=(wall.x+wall.w)-player.x;

So now we can fix collision to the right or left of the player. There are only one thing we need to do before implementing the collision for the y axis. We need to be able to know which direction the player is going, since we can't get proper collision by running both of these at the same time. Note: There are different methods to determine which direction the player is, this method, in my opinion, is the easiest method to use.

The way we're going to find the direction is by storing where the player is before we move him.

Code:
1    oldx=player.x;
2    player.x+=5;

We then see if there is a collision and if there is we check if oldx is less than or greater than player.x. greater=moving right, less=moving left

Code:
1    if(oldx<player.x) {player.x-=(player.x+player.w)-wall.x;}
2    if(oldx>player.x) {player.x+=(wall.x+wall.w)-player.x;}

And there we go! Moving left or right will properly move the player back.
When you implement collision to the Y axis (up and down collision), all you need to do is replace the values.

For example:
Code:
1    if(oldx<player.x) {player.x-=(player.x+player.w)-wall.x;}

is:

Code:
1    if(oldy<player.y) {player.y-=(player.y+player.h)-wall.y;}

We are know almost done. The last thing I need to show you is how to implement this code correctly. To have correctly working collision you need to check collision to the x axis, and then the y axis. DO NOT check both at the same time, as this will cause buggy collision.

Code:
//Move X
1    if(oldx<player.x) {player.x-=(player.x+player.w)-wall.x;}
2    if(oldx>player.x) {player.x+=(wall.x+wall.w)-player.x;}

//Move Y
3    if(oldy<player.y) {player.y-=(player.y+player.h)-wall.y;}
4    if(oldy>player.y) {player.y+=(wall.y+wall.h)-player.y;}

And now you're all done!

Note: The source code does not have the exact same code, but it still should help you if you are having problems, also you need to have SDL2 downloaded and insert SDL2.dll into the compiled program file.

No comments: