Last Updated 2/23/06
In the last tutorial we had to use 11 collision boxes for a circle. This tutorial will teach you a more efficient way to handle circles and collision detection.
#include "SDL/SDL.h" #include "SDL/SDL_image.h" #include #include #include
This tutorial is going to require the distance formula so we include a header for math.
//A circle stucture struct Circle { int x, y; int r; };
We have to create our own circle structure for this program. "x" and "y" are the offsets of the center of the circle. "r" is the radius.
//The dot class Dot { private: //The area of the dot Circle c; //The velocity of the dot int xVel, yVel; public: //Initializes the variables Dot(); //Takes key presses and adjusts the dot's velocity void handle_input(); //Moves the dot void move( std::vector &rects, Circle &circle ); //Shows the dot on the screen void show(); };
Here's yet another revision of the Dot class.
Everything is pretty much the same except for two differences. This time we have a Circle structure instead of a vector of SDL_Rects. Also, in the move() function we check collision with a vector of SDL_Rects and a Circle.
double distance( int x1, int y1, int x2, int y2 ) { //Return the distance between the two points return sqrt( pow( x2 - x1, 2 ) + pow( y2 - y1, 2 ) ); }
This function we made gives us the distance between given 2 points. This is pretty much the only real math used in this program.
bool check_collision( Circle &A, Circle &B ) { //If the distance between the centers of the circles is less than the sum of their radii if( distance( A.x, A.y, B.x, B.y ) < ( A.r + B.r ) ) { //The circles have collided return true; } //If not return false; }
Checking collision between two circles is pretty easy. All you have to do is check whether or not the distance between the centers of the circles is less than the sum of their radii.
If it is less, a collision has happened, otherwise there's no collision.
bool check_collision( Circle &A, std::vector &B ) { //The sides of the shapes int leftAv, leftAh, leftB; int rightAv, rightAh, rightB; int topAv, topAh, topB; int bottomAv, bottomAh, bottomB; //The corners of box B int Bx1, By1; int Bx2, By2; int Bx3, By3; int Bx4, By4;
This function check collision between a circle and a vector of rectangles. Checking for collision between a circle and a rectangle gets a bit tricky.
There's essentially two types of possible collisions:
The circle can either touch a side or a corner of the rectangle.
So we have to take into account the sides of the circle, the sides of the rectangle and corners of the rectangle.
//Calculate the sides of A leftAv = A.x; rightAv = A.x; topAv = A.y - A.r; bottomAv = A.y + A.r; leftAh = A.x - A.r; rightAh = A.x + A.r; topAh = A.y; bottomAh = A.y;
You may be wondering why we have two sets of sides for the circle.
When we check for collision along the sides of the circle, we form a cross:
So when we check for circle side collisions, we check if the box collides with either of the lines.
How do you check collision with a line? A line is just a rectangle with a thickness of one, so all of the same principles apply.
//Go through the B boxes for( int Bbox = 0; Bbox < B.size(); Bbox++ ) { //Calculate the sides of rect B leftB = B[ Bbox ].x; rightB = B[ Bbox ].x + B[ Bbox ].w; topB = B[ Bbox ].y; bottomB = B[ Bbox ].y + B[ Bbox ].h; //Calculate the corners of B Bx1 = B[ Bbox ].x, By1 = B[ Bbox ].y; Bx2 = B[ Bbox ].x + B[ Bbox ].w, By2 = B[ Bbox ].y; Bx3 = B[ Bbox ].x, By3 = B[ Bbox ].y + B[ Bbox ].h; Bx4 = B[ Bbox ].x + B[ Bbox ].w, By4 = B[ Bbox ].y + B[ Bbox ].h;
Now that we have calculated the sides of the circle, it's time to go through the rectangles.
First we calculate the sides and corners of the rectangle.
//If no sides from vertical A are outside of B if( ( ( bottomAv <= topB ) || ( topAv >= bottomB ) || ( rightAv <= leftB ) || ( leftAv >= rightB ) ) == false ) { //A collision is detected return true; } //If no sides from horizontal A are outside of B if( ( ( bottomAh <= topB ) || ( topAh >= bottomB ) || ( rightAh <= leftB ) || ( leftAh >= rightB ) ) == false ) { //A collision is detected return true; }
Then we check if the rectangle collides with the vertical or horizontal lines of the circle.
//If any of the corners from B are inside A if( ( distance( A.x, A.y, Bx1, By1 ) < A.r ) || ( distance( A.x, A.y, Bx2, By2 ) < A.r ) || ( distance( A.x, A.y, Bx3, By3 ) < A.r ) || ( distance( A.x, A.y, Bx4, By4 ) < A.r ) ) { //A collision is detected return true; } } //If the shapes have not collided return false; }
After that we check if any of the corners from the rectangle are inside the circle.
If the function goes through and no rectangles from the vector have collided with circle, the function return false.
//Make the shapes std::vector box( 1 ); Circle otherDot; //Set the shapes' attributes box[ 0 ].x = 60; box[ 0 ].y = 60; box[ 0 ].w = 40; box[ 0 ].h = 40; otherDot.x = 30; otherDot.y = 30; otherDot.r = DOT_WIDTH / 2;
In the main() function we create the 2 shapes the Dot is going to interact with.
//While the user hasn't quit while( quit == false ) { //Start the frame timer fps.start(); //While there's events to handle while( SDL_PollEvent( &event ) ) { //Handle events for the dot myDot.handle_input(); //If the user has Xed out the window if( event.type == SDL_QUIT ) { //Quit the program quit = true; } } //Fill the screen white SDL_FillRect( screen, &screen->clip_rect, SDL_MapRGB( screen->format, 0xFF, 0xFF, 0xFF ) ); //Move the dot myDot.move( box, otherDot ); //Show the box SDL_FillRect( screen, &box[ 0 ], SDL_MapRGB( screen->format, 0x00, 0x00, 0x00 ) ); //Show the other dot apply_surface( otherDot.x - otherDot.r, otherDot.y - otherDot.r, dot, screen ); //Show our dot myDot.show(); //Update the screen if( SDL_Flip( screen ) == -1 ) { return 1; } //Cap the frame rate while( fps.get_ticks() < 1000 / FRAMES_PER_SECOND ) { //wait } }
Here's the main loop, pretty much everything is the same story as before. I would like to point one thing out.
When we show the other dot, we don't blit the image at its offset. The offset is the center of the circle, so we have to blit the dot image at the upper-left corner. We do this by subtracting the radius from the offset.