Allegro.cc - Online Community

Allegro.cc Forums » Programming Questions » Best way for collision detection/bounding box collision

This thread is locked; no one can reply to it. rss feed Print
 1   2 
Best way for collision detection/bounding box collision
Lasaqus7
Member #15,950
May 2015

My level background is a bitmap with various obstacles on it

Whats the best way to say just put a square over that image and stop my player from moving into them

I started putting pink squares/rectangles over the item I want collision with, to get the x1, x2, y1, y2 co-ordinates but there are maybe around 25 squares and rectangles.

Do i just do it 1 by one or does it in an array (not done this before)

Also How do I overcome this

#SelectExpand
1if (Human.y < 90 && Human.x < 860) //Check to see if he has hit a bound 2 { 3 Human.y = 90; //Stop him going past the up bound 4 } 5 if (Human.x < 860 && Human.y < 90) 6 { 7 Human.x = 860; //Stop him going past the right bound 8//What happens is that if I approach from this side regardless of where I am at my player gets set to the above Human.y value. 9 }

I know I need to somehow stop him moving in the x/y direction but I still need him to move in the other x/y direction

EDIT:- I found this forum post and going to try following it https://www.allegro.cc/forums/thread/613787

Chris Katko
Member #1,881
January 2002
avatar

If you are deciding whether two OBJECTS are colliding

for each objectA in array of objects
   for each objectB in array of objects
     if(objectA != objectB) //we don't want to collide with ourself!
// do bounding box collision

I don't recall the exact formula off the top of my head for bounding boxes, but the point here is you are doing an O(n^2) ("n squared") algorithm that means the number of operations is equal to the square of the number of objects in the array.

You only need more elegant solutions when number of objects become very high. (1000 or more.)

As for the geometry of whether or not the collision has occured:

https://wiki.allegro.cc/index.php?title=Bounding_Box

http://gamedev.stackexchange.com/questions/586/what-is-the-fastest-way-to-work-out-2d-bounding-box-intersection

If you are deciding whether an OBJECT is colliding with a BITMAP

You can use pixel-perfect collision detection (each pixel is tested), or you can use what I'll call "finite point" method where you a certain number of points that are relative to the center x,y position. So if you had a guy that was twice as tall as he was wide, you would test say [x,y-10],[x,y+10],[x-5,y],[x+5,y]. The number of points and their positions can very. So you can use more for a bigger object, or even change their positions as the sprites change. It's almost as good as pixel perfect, much faster, and in some cases, can behave even better. (A single stray pixel won't cause problems, only the pixel locations you want.)

[edit] I think I'm going to learn HTML5/JS/whatever and then throw together some scripted examples so people can see/feel how they interact.

-----sig:
“Programs should be written for people to read, and only incidentally for machines to execute.” - Structure and Interpretation of Computer Programs
"Political Correctness is fascism disguised as manners" --George Carlin

Lasaqus7
Member #15,950
May 2015

Cheers Chris, I'll have a read through those pages and see what I can work out

l j
Member #10,584
January 2009
avatar

I'd like to point out that the easiest collision detection is between points-circles and circles-circles, they only require very simple distance checks.

beoran
Member #12,636
March 2011

Some untested code for common collision cases:

#SelectExpand
1int collide_point_circle(double px, double py, double cx, double cy, double r) { 2 /* Square of the distance between the circle's center and the point from Pythagroras' theorem. */ 3 double d2 = (cx - px) * (cx - px) + (cy - py) * (cy - py); 4 /* collision if the square of the distance is less than the square 5 of the radius. */ 6 return (d2 <= (r*r)); 7} 8 9int collide_circle_circle(double c1x, double c1y, double r1 double c2x, double c2y, double r2) { 10 /* Collision between circles if the distance between the centers is less than the sum of the radii. */ 11 return collide_point_circle(c1x, c1y, c2x, c2y, r1+r2); 12} 13 14/** AABB is an axis aligned bounds box, that is, rectangle that is aligned with the x and y axis of the display. rw and rh are width and heigt, respectively.*/ 15int collide_point_aabb(double px, double py, double rx, double ry, double rw, double rh) { 16 if (px < rx) return 0; 17 if (px > (rx+rw)) return 0; 18 if (py < ry) return 0; 19 if (py > (ry+rh)) return 0; 20 return !0; 21} 22 23int collide_aabb_aabb(double r1x, double r1y, double r1w, double r1h, double r2x, double r2y, double r2w, double r2h) { 24 if ((r1x + r1w) < r2x) return 0; 25 if (r1x > (r2x+r2w)) return 0; 26 if ((r1y + r1h) < r2y) return 0; 27 if (r1y > (r2y+r2h)) return 0; 28 return !0; 29}

Lasaqus7
Member #15,950
May 2015

I'm trying to get this collision working and it doesn't seem to be going right

#SelectExpand
1const int WIDTH = 800; 2const int HEIGHT = 600; 3 4enum KEYS{ UP, DOWN, LEFT, RIGHT, ESCAPE, W, A, S, D, SPACE}; 5bool keys[] = { false, false, false, false, false, false, false, false, false, false }; 6 7struct Player 8{ 9 int ID; 10 int x; 11 int y; 12 int bx; 13 int by; 14 int w = 48; 15 int h = 48; 16 int speed = 5; 17 18 ALLEGRO_BITMAP *image; 19}; 20Player Human1; 21Player Human2; 22void MoveHumanUp(Player &Human); 23void MoveHumanDown(Player &Human); 24void MoveHumanLeft(Player &Human); 25void MoveHumanRight(Player &Human); 26 27int BoundingBox(int b1_x, int b1_y, int b1_w, int b1_h, int b2_x, int b2_y, int b2_w, int b2_h) 28{ 29 if ((b1_x > b2_x + b2_w - 1) || (b1_y > b2_y + b2_h - 1) || (b2_x > b1_x + b1_w - 1) || (b2_y > b1_y + b1_h - 1)) 30 { 31 //no collision 32 return 0; 33 } 34 //collision 35 return 1; 36} 37 38int main(int argc, char **argv) 39{ 40 //========================================== 41 //PROJECT VARIABLES 42 //========================================== 43 bool done = false; 44 bool render = false; 45 46 bool bound = false; 47 bool collision = false; 48 49 Player Human1; 50 Player Human2; 51 52 Human1.x = 0; 53 Human1.y = 0; 54 55 Human2.x = WIDTH / 2; 56 Human2.y = HEIGHT / 2; 57 58 int b1_x; 59 int b1_y; 60 int b1_w; 61 int b1_h; 62 63 int b2_x; 64 int b2_y; 65 int b2_w; 66 int b2_h; 67 68 b1_x = Human1.x; 69 b1_y = Human1.y; 70 b1_w = Human1.w; 71 b1_h = Human1.h; 72 73 b2_x = Human2.x; 74 b2_y = Human2.y; 75 b2_w = Human2.w; 76 b2_h = Human2.h; 77 78 79 80 81 //========================================== 82 //ALLEGRO VARIABLES 83 //========================================== 84 ALLEGRO_DISPLAY *display = NULL; 85 ALLEGRO_EVENT_QUEUE *event_queue = NULL; 86 ALLEGRO_TIMER *timer; 87 ALLEGRO_FONT *font18 = NULL; 88 89 //========================================== 90 //ALLEGRO INIT FUNCTIONS 91 //========================================== 92 if (!al_init()) 93 return -1; 94 95 display = al_create_display(WIDTH, HEIGHT); 96 97 if (!display) 98 return -1; 99 100 //========================================== 101 //ADDON INSTALL 102 //========================================== 103 al_install_keyboard(); 104 al_init_image_addon(); 105 al_init_font_addon(); 106 al_init_ttf_addon(); 107 al_init_primitives_addon(); 108 109 //========================================== 110 //PROJECT INIT 111 //========================================== 112 font18 = al_load_font("arial.ttf", 18, 0); 113 114 Human1.image = al_load_bitmap("NPC1.bmp"); 115 al_convert_mask_to_alpha(Human1.image, al_map_rgb(105, 74, 46)); 116 Human2.image = al_load_bitmap("NPC1.bmp"); 117 al_convert_mask_to_alpha(Human2.image, al_map_rgb(105, 74, 46)); 118 119 Human1.w = al_get_bitmap_width(Human1.image); 120 Human1.h = al_get_bitmap_height(Human1.image); 121 122 Human2.w = al_get_bitmap_width(Human2.image); 123 Human2.h = al_get_bitmap_height(Human2.image); 124 125 //========================================== 126 //TIMER INIT AND STARTUP 127 //========================================== 128 event_queue = al_create_event_queue(); 129 timer = al_create_timer(1.0 / 60); 130 131 al_register_event_source(event_queue, al_get_timer_event_source(timer)); 132 al_register_event_source(event_queue, al_get_keyboard_event_source()); 133 134 al_start_timer(timer); 135 while (!done) 136 { 137 ALLEGRO_EVENT ev; 138 al_wait_for_event(event_queue, &ev); 139 140 //========================================== 141 //INPUT 142 //========================================== 143 if (ev.type == ALLEGRO_EVENT_KEY_DOWN) 144 { 145 switch(ev.keyboard.keycode) 146 { 147 case ALLEGRO_KEY_ESCAPE: 148 done = true; 149 break; 150 case ALLEGRO_KEY_UP: 151 keys[UP] = true; 152 break; 153 case ALLEGRO_KEY_DOWN: 154 keys[DOWN] = true; 155 break; 156 case ALLEGRO_KEY_LEFT: 157 keys[LEFT] = true; 158 break; 159 case ALLEGRO_KEY_RIGHT: 160 keys[RIGHT] = true; 161 break; 162 case ALLEGRO_KEY_SPACE: 163 keys[SPACE] = true; 164 break; 165 case ALLEGRO_KEY_W: 166 keys[W] = true; 167 break; 168 case ALLEGRO_KEY_S: 169 keys[S] = true; 170 break; 171 case ALLEGRO_KEY_A: 172 keys[A] = true; 173 break; 174 case ALLEGRO_KEY_D: 175 keys[D] = true; 176 break; 177 } 178 } 179 else if (ev.type == ALLEGRO_EVENT_KEY_UP) 180 { 181 switch(ev.keyboard.keycode) 182 { 183 case ALLEGRO_KEY_ESCAPE: 184 done = true; 185 break; 186 case ALLEGRO_KEY_UP: 187 keys[UP] = false; 188 break; 189 case ALLEGRO_KEY_DOWN: 190 keys[DOWN] = false; 191 break; 192 case ALLEGRO_KEY_LEFT: 193 keys[LEFT] = false; 194 break; 195 case ALLEGRO_KEY_RIGHT: 196 keys[RIGHT] = false; 197 break; 198 case ALLEGRO_KEY_SPACE: 199 keys[SPACE] = false; 200 break; 201 case ALLEGRO_KEY_W: 202 keys[W] = false; 203 break; 204 case ALLEGRO_KEY_S: 205 keys[S] = false; 206 break; 207 case ALLEGRO_KEY_A: 208 keys[A] = false; 209 break; 210 case ALLEGRO_KEY_D: 211 keys[D] = false; 212 break; 213 } 214 } 215 //========================================== 216 //GAME UPDATE 217 //========================================== 218 else if (ev.type == ALLEGRO_EVENT_TIMER) 219 { 220 render = true; 221 if (BoundingBox(b1_x, b1_y, b1_w, b1_h, b2_x, b2_y, b2_w, b2_h) == 0) 222 { 223 if (keys[UP] || keys[W]) 224 MoveHumanUp(Human1); 225 else if (keys[DOWN] || keys[S]) 226 MoveHumanDown(Human1); 227 else if (keys[LEFT] || keys[A]) 228 MoveHumanLeft(Human1); 229 else if (keys[RIGHT] || keys[D]) 230 MoveHumanRight(Human1); 231 } 232 } 233 //========================================== 234 //RENDER 235 //========================================== 236 if(render && al_is_event_queue_empty(event_queue)) 237 { 238 render = false; 239 240 al_draw_bitmap(Human2.image, Human2.x - Human2.w / 2, Human2.y - Human2.h / 2, 0); 241 al_draw_bitmap(Human1.image, Human1.x - Human1.w / 2, Human1.y - Human1.h / 2, 0); 242 al_flip_display(); 243 al_clear_to_color(al_map_rgb(0, 0, 0)); 244 } 245 } 246 //========================================== 247 //DESTROY ALLEGRO OBJECTS 248 //========================================== 249 al_destroy_bitmap(Human1.image); 250 al_destroy_bitmap(Human2.image); 251 al_destroy_font(font18); 252 al_destroy_timer(timer); 253 al_destroy_event_queue(event_queue); 254 al_destroy_display(display); 255 256 return 0; 257} 258void MoveHumanUp(Player &Human1) 259{ 260 Human1.y -= Human1.speed; 261 if (Human1.y < 0) 262 Human1.y = 0; 263} 264void MoveHumanDown(Player &Human1) 265{ 266 Human1.y += Human1.speed; 267 if (Human1.y > HEIGHT) 268 Human1.y = HEIGHT; 269} 270void MoveHumanLeft(Player &Human1) 271{ 272 Human1.x -= Human1.speed; 273 if (Human1.x < 0) 274 Human1.x = 0; 275} 276void MoveHumanRight(Player &Human1) 277{ 278 Human1.x += Human1.speed; 279 if (Human1.x > WIDTH) 280 Human1.x = WIDTH; 281}

Not sure if I got things in the wrong place or just doing it wrong entirely

beoran
Member #12,636
March 2011

You're not using the BoundingBox function correctly. It should be used to check whether the player overlaps with something when a key is pressed by checking for the future (one step ahead) position of the player versus the future position of the game objects and walls. Also don't case the bounds in variables, use the player and game object's positions directly.

Lasaqus7
Member #15,950
May 2015

Thanks beoran. After that I re worked the code and I do have collision being detected and correctly stops my player on the collision.

However, once collision had been detected I cannot move my player anymore.

Removed alot of code and left the Collision section in

#SelectExpand
1bool hasCollision = false; 2 3struct Player 4{ 5 int ID; 6 int x; 7 int y; 8 int bx; 9 int by; 10 int w = 48; 11 int h = 48; 12 int speed = 5; 13 14 ALLEGRO_BITMAP *image; 15}; 16Player Human1; 17Player Human2; 18void MoveHumanUp(Player &Human); 19void MoveHumanDown(Player &Human); 20void MoveHumanLeft(Player &Human); 21void MoveHumanRight(Player &Human); 22void Collision(Player &Human1, Player &Human2); 23 24int main(int argc, char **argv) 25{ 26 else if (ev.type == ALLEGRO_EVENT_TIMER) 27 { 28 render = true; 29 30 if (keys[UP] || keys[W]) 31 { 32 Collision(Human1, Human2); 33 if (!hasCollision) 34 MoveHumanUp(Human1); 35 } 36 else if (keys[DOWN] || keys[S]) 37 { 38 Collision(Human1, Human2); 39 if (!hasCollision) 40 MoveHumanDown(Human1); 41 } 42 else if (keys[LEFT] || keys[A]) 43 { 44 Collision(Human1, Human2); 45 if (!hasCollision) 46 MoveHumanLeft(Human1); 47 } 48 else if (keys[RIGHT] || keys[D]) 49 { 50 Collision(Human1, Human2); 51 if (!hasCollision) 52 MoveHumanRight(Human1); 53 } 54 } 55 } 56 return 0; 57} 58void Collision(Player &Human1, Player &Human2) 59{ 60 hasCollision = true; 61 62 if (Human1.x > Human2.x + Human2.w - 1) 63 hasCollision = false; 64 if (Human1.y > Human2.y + Human2.h - 1) 65 hasCollision = false; 66 if (Human2.x > Human1.x + Human1.w - 1) 67 hasCollision = false; 68 if (Human2.y > Human1.y + Human1.h - 1) 69 hasCollision = false; 70}

I think I understand it, As it now calls to check collision after a key press and before movement. I also changed the variable to hold the player rather than extra variables.

Although I did change everything into a function now, (Hope that's heading along the right line).

beoran
Member #12,636
March 2011

That global variable hasCollision is a bad idea, it's bound to get out of date. In stead, make the Collide function return an int for C89 or bool for C++ or C99, and then check that in your if.

Secondly, the reason your player is getting stuck is because Collision checks the current position of the objects. This means that once a collision happens the object will not be able to move anymore because it will always keep colliding since the overlap already happened, like bullet stuck in the wall. It's necessary to check the next position of the game objects by taking their speed into consideration.

Alternatively, you can leave the collision as it is now, but then, you have to move the player back out of collision whenever collision happens.

Lasaqus7
Member #15,950
May 2015

Hope I'm heading in the right direction which this now

I removed the global hasCollision

#SelectExpand
1//Player movement 2if (keys[UP] || keys[W]) 3 { 4 if (Collision(Human1, Human2) == false) 5 MoveHumanUp(Human1); 6 else 7 //Confused here at the moment 8 Human1.y += Human1.speed; 9 } 10 11//My function now a boolean 12bool Collision(Player &Human1, Player &Human2) 13{//I hope this is what is meant by checking the future position 14 15 if (Human1.x + Human1.speed > Human2.x + Human2.w - 1) 16 return false; 17 if (Human1.y + Human1.speed > Human2.y + Human2.h - 1) 18 return false; 19 if (Human2.x > Human1.x - Human1.speed + Human1.w - 1) 20 return false; 21 if (Human2.y > Human1.y - Human1.speed + Human1.h - 1) 22 return false; 23 else 24 return true; 25}

I know the Human1.y += Human1.speed; is wrong at the moment and as such make my character's controls reserve after collision, which I'm trying to fix.

Although now I can collide with something and still move (albeit in the wrong direction haha).

Does this look slightly better now ?

beoran
Member #12,636
March 2011

Yes, this looks better. What you are doing is actually not so bad at all. There are several ways to do collision handling. The main distinction between these ways is how objects are kept from getting stuck.

This can be done in general in two ways:

  • Check where the objects will be after moving and if they will collide, don't move them.

  • Check if the objects are colliding and were moving before. If thy collide and were moving, put the objects back at their previous position.

The way you are trying is the second correcting approach, which can work if done correctly. In such an approach you could give the player struct an extra old_x and old_y to keep track of the previous position and restore that on collision.

Hope that helps. :)

Lasaqus7
Member #15,950
May 2015

Thanks Beoran, long delay as I had stayed up all night trying to get my head around all this bounding box/collision stuff, and so many people write things which are slightly different or totally different to how I have it done and then I get confused ;D.

I have tried to add in the old_x and old_y value which you mentioned although I still get stuck on the bounding box.

There is this i found which I think explains what you said about old_x and old_y

#SelectExpand
1vector2 oldPos = currentPos; 202 3 403 5vector2 newPos = currentPos + someValue; 604 7 805 9if(collides(currentPos)) 1006 11{ 1207 13 currentPos = oldPos; 1408 15} 1609 17else 1810 19{ 2011 21 currentPos = newPos; 2212 23} 2413 25 2614 27// draw at currentPos

Which didn't seem to work for me

#SelectExpand
1else if (ev.type == ALLEGRO_EVENT_TIMER) 2 { 3 render = true; 4 5 Human1.oldx = Human1.x; //This is his vector2 oldPos = curPos 6 Human1.oldy = Human1.y; 7 8 if (keys[UP] || keys[W]) 9 { 10 11 if (Collision(Human1, Human2) == false) 12 MoveHumanUp(Human1); // And this would be his currentPos = newPos; 13 else 14 { 15 Human1.y = Human1.oldy; //And he currentPos = oldPos; 16 } 17 } 18 19// My move up function, I have 4 one for each x,y direction (not the best way, just how Im learning) 20void MoveHumanUp(Player &Human1) 21{ 22 Human1.y -= Human1.speed; 23 if (Human1.y < 0) 24 Human1.y = 0; 25}

He states it should work, but I just get stuck again.

beoran
Member #12,636
March 2011

You're doing this too soon!

Human1.oldx = Human1.x; 
Human1.oldy = Human1.y;

You can only do this after checking that Human1 is not colliding with anything at it's x and y position. Otherwise you're just storing the "stuck" coordinates in the oldx and oldy , which makes them useless.

Lasaqus7
Member #15,950
May 2015

Got it now, Beoran ty.

You make me think :-)

I do like to know that code is correct and what the error is and it helps me figure it out, sometimes it just takes forever, but I have a somewhat working collision

Although (Sorry another issue)

My player now kinda bounces back and forth between pixels on collision and if I release the movement key while he is furthest away from the collision he can carry on moving as normal, but if i release the movement key when he is closest to collision he gets stuck.

What area of the code would/could causes this issue ?

I've tried to add/subtract numbers from various x/y values to compensate for this but haven't fixed it yet

EDIT, I kinda got the bouncing to stop, but it then stops me moving in the other axis, so if I collided --> i cant move up or down. Still working on it though :-)

EDIT AGAIN, I think I have solved it now, seems to be working fine.

EDIT AGAIN: Any idea how I go about adding around 20 different x, y, w, h values for Human2 (There not humans, just keeping it that way for this purpose) Mine seems to be a long long way

#SelectExpand
1void Human2Bounds(Player Human2[], int size) 2{ 3 for (int i = 0; i < size; i++) 4 { 5 Human2[0].x = 0; 6 Human2[0].y = 0; 7 Human2[0].w = 860; 8 Human2[0].h = 90; 9 10 Human2[1].x = 920; 11 Human2[1].y = 0; 12 Human2[1].w = 1280; 13 Human2[1].h = 210; 14 } 15}

Chris Katko
Member #1,881
January 2002
avatar

In C++, you normally set initial conditions with a constructor.

#SelectExpand
1class human{ 2 3public: //ignore this for now, it just means anyone can access the 4//following code from anywhere in the program. 5 6float x; 7float y; 8 9//default constructor, note no return name OR void. Takes no arguments. 10//This one is automatically made by the compiler (and empty) unless you 11//override it. It's ALWAYS called when you make an object. 12human() 13{ 14x = 200; //all humans spawn here unless specified (see next) 15y = 300; 16} 17 18 //custom constructor, same as before but allows you to specify arguments. 19human(int x, int y) 20{ 21human::x = x; //human:: selects the HUMAN class variable "x", needed because we 22// also have a FUNCTION "x". The other solution is to just use different 23// names like _x and _y for all class member variables so you can always 24// tell the difference. 25 26human::y = y; 27} 28 29} 30 31int main() 32{ 33human new_human; //will be at 200,300 34human new_human2(350,350); //will be at 350,350 35 36human human_array[100]; //CANNOT use a custom constructor here. How would you specify them? 37//Solutions: 38 39human *human_array[10]; //array of ten pointers to UNASSIGNED humans. Need to create AND assign them. 40 41for(int i = 0; i< 10; i++) 42{ 43human next_human = new human(352, 250); //can call custom constructor! 44human_array[i] = next_human; //array position i now points to our next human. 45} //this creates ten humans all at 352, 250, but we could specify 46// anything else such as using rand() to randomly populate them. 47 48//CAVEAT. Anything that has a "new" MUST have a "delete." 49//Because they don't disappear when they go out of scope 50// like a local (non new) variable will. 51for(int i = 0; i< 10; i++) 52{ 53delete human_array[i]; 54} 55 56}

[edit] DOH, I confused your post with someone else, so I thought you were using C++.

#SelectExpand
1void Human2Bounds(Player Human2[], int size) 2{ 3 for (int i = 0; i < size; i++) 4 { 5 Human2[0].x = 0; 6 Human2[0].y = 0; 7 Human2[0].w = 860; 8 Human2[0].h = 90; 9 10 Human2[1].x = 920; 11 Human2[1].y = 0; 12 Human2[1].w = 1280; 13 Human2[1].h = 210; 14 } 15}

This actually works fine. But it should be like this:

void Human2Bounds(Player Human2[], int size)
{
  for (int i = 0; i < size; i++)
  {
    Human2[i].x = 0; //note i
    Human2[i].y = 0;
    Human2[i].w = 860;
    Human2[i].h = 90;

  }
}

I don't understand what "human2bounds" means, but if you're just setting up coordinates that will work. The gigantic width also confuses me.

Now as for specific coordinates. You can read in a list of coordinates from a file, from a hand-written array, or, the easiest, use rand() to randomly select positions:

void Human2Bounds(Player Human2[], int size)
{
  for (int i = 0; i < size; i++)
  {
    Human2[i].x = rand()%300; //rand() returns 0 to 299
    Human2[i].y = rand()%300;
    Human2[i].w = 860;
    Human2[i].h = 90;

  }
}

-----sig:
“Programs should be written for people to read, and only incidentally for machines to execute.” - Structure and Interpretation of Computer Programs
"Political Correctness is fascism disguised as manners" --George Carlin

Lasaqus7
Member #15,950
May 2015

Tbh I posted this with 2 human images on to try and get the bounding box collision working, now that seems to be fine I want to try and implement the code into my game.

Its nothing to to with a Human, the bounding boxes will be for terrain on screen I want collision on (hence the 25 items and strange sizes)

I just thought I'd keep with the "Humans" so if anyone looked over previous code they could kinda understand it more.

What I have been doing is drawing rectangles and squares on screen for these items and just need to add them into an array for the bounding boxes.

Ill read over you help and try and get it working.

As of now I can only get 1 of the rectangles to work any others I add in the player just goes through them

Edgar Reynaldo
Major Reynaldo
May 2007
avatar

You have to compare every moving object with every other object it could possibly collide with. There are ways to prune the number of potential collisions but that's a more advanced topic.

What you want is a Rectangle struct that stores the rectangle of whatever kind of object needs one. Then you compare rectangles instead of humans.

Lasaqus7
Member #15,950
May 2015

This is what I have as of now

#SelectExpand
1#include <allegro5/allegro.h> 2#include <allegro5/allegro_image.h> 3#include <allegro5/allegro_primitives.h> 4#include <allegro5/allegro_font.h> 5#include <allegro5/allegro_ttf.h> 6 7const int WIDTH = 1280; 8const int HEIGHT = 720; 9const int NUM_BOUNDS = 3; 10enum KEYS{ W, A, S, D, SPACE}; 11bool keys[] = { false, false, false, false, false, false, false, false, false, false }; 12 13struct Player 14{ 15 int ID; 16 int x; 17 int y; 18 int bx; 19 int by; 20 int oldx; 21 int oldy; 22 int w = 48; 23 int h = 48; 24 int speed = 4; 25 26 ALLEGRO_BITMAP *image; 27}; 28 29struct Bounds 30{ //I guess this is what you mean by a rectangle struct that stores the information 31 int x; 32 int y; 33 int w; 34 int h; 35}; 36Player Human1; 37Bounds b2[3]; 38 39void MoveHumanUp(Player &Human); 40void MoveHumanDown(Player &Human); 41void MoveHumanLeft(Player &Human); 42void MoveHumanRight(Player &Human); 43bool Collision2(Player &Human1, Bounds b2[], int size); 44void BoundingBoxes(); 45void TestBounds(Bounds b2[], int size); 46 47int main(int argc, char **argv) 48{ 49 //PROJECT VARIABLES 50 bool done = false; 51 bool render = false; 52 53 Player Human1; 54 55 Human1.x = 50; 56 Human1.y = 400; 57 58 int b1_x; 59 int b1_y; 60 int b1_w; 61 int b1_h; 62 63 b1_x = Human1.x; 64 b1_y = Human1.y; 65 b1_w = Human1.w; 66 b1_h = Human1.h; 67 68 Human1.oldy = Human1.y; 69 Human1.oldx = Human1.x; 70 71 //ALLEGRO VARIABLES 72 ALLEGRO_DISPLAY *display = NULL; 73 ALLEGRO_EVENT_QUEUE *event_queue = NULL; 74 ALLEGRO_TIMER *timer; 75 ALLEGRO_FONT *font18 = NULL; 76 77 //ALLEGRO INIT FUNCTIONS 78 if (!al_init()) 79 return -1; 80 81 display = al_create_display(WIDTH, HEIGHT); 82 83 if (!display) 84 return -1; 85 86 //ADDON INSTALL 87 al_install_keyboard(); 88 al_init_image_addon(); 89 al_init_font_addon(); 90 al_init_ttf_addon(); 91 al_init_primitives_addon(); 92 93 //PROJECT INIT 94 font18 = al_load_font("arial.ttf", 18, 0); 95 96 Human1.image = al_load_bitmap("NPC1.bmp"); 97 al_convert_mask_to_alpha(Human1.image, al_map_rgb(105, 74, 46)); 98 99 Human1.w = al_get_bitmap_width(Human1.image); 100 Human1.h = al_get_bitmap_height(Human1.image); 101 102 Human1.bx = Human1.w / 2; 103 Human1.by = Human1.h / 2; 104 105 //TIMER INIT AND STARTUP 106 event_queue = al_create_event_queue(); 107 timer = al_create_timer(1.0 / 60); 108 109 al_register_event_source(event_queue, al_get_timer_event_source(timer)); 110 al_register_event_source(event_queue, al_get_keyboard_event_source()); 111 112 al_start_timer(timer); 113 while (!done) 114 { 115 ALLEGRO_EVENT ev; 116 al_wait_for_event(event_queue, &ev); 117 118 //INPUT 119 if (ev.type == ALLEGRO_EVENT_KEY_DOWN) 120 { 121 switch(ev.keyboard.keycode) 122 { 123 case ALLEGRO_KEY_ESCAPE: 124 done = true; 125 break; 126 case ALLEGRO_KEY_W: 127 keys[W] = true; 128 break; 129 case ALLEGRO_KEY_S: 130 keys[S] = true; 131 break; 132 case ALLEGRO_KEY_A: 133 keys[A] = true; 134 break; 135 case ALLEGRO_KEY_D: 136 keys[D] = true; 137 break; 138 } 139 } 140 else if (ev.type == ALLEGRO_EVENT_KEY_UP) 141 { 142 switch(ev.keyboard.keycode) 143 { 144 case ALLEGRO_KEY_ESCAPE: 145 done = true; 146 break; 147 case ALLEGRO_KEY_W: 148 keys[W] = false; 149 break; 150 case ALLEGRO_KEY_S: 151 keys[S] = false; 152 break; 153 case ALLEGRO_KEY_A: 154 keys[A] = false; 155 break; 156 case ALLEGRO_KEY_D: 157 keys[D] = false; 158 break; 159 } 160 } 161 //GAME UPDATE 162 else if (ev.type == ALLEGRO_EVENT_TIMER) 163 { 164 render = true; 165 TestBounds(b2, NUM_BOUNDS); 166 167 if (keys[W]) 168 { 169 if (Collision2(Human1, b2, NUM_BOUNDS) == false) 170 { 171 Human1.oldy = Human1.y; 172 Human1.oldx = Human1.x; 173 MoveHumanUp(Human1); 174 } 175 else 176 { 177 Human1.y = Human1.oldy; 178 Human1.x = Human1.oldx; 179 } 180 } 181 else if (keys[S]) 182 { 183 if (Collision2(Human1, b2, NUM_BOUNDS) == false) 184 { 185 Human1.oldy = Human1.y; 186 Human1.oldx = Human1.x; 187 MoveHumanDown(Human1); 188 } 189 else 190 { 191 Human1.y = Human1.oldy; 192 Human1.x = Human1.oldx; 193 } 194 } 195 else if (keys[A]) 196 { 197 if (Collision2(Human1, b2, NUM_BOUNDS) == false) 198 { 199 Human1.oldy = Human1.y; 200 Human1.oldx = Human1.x; 201 MoveHumanLeft(Human1); 202 } 203 else 204 { 205 Human1.y = Human1.oldy; 206 Human1.x = Human1.oldx; 207 } 208 } 209 else if (keys[D]) 210 { 211 if (Collision2(Human1, b2, NUM_BOUNDS) == false) 212 { 213 Human1.oldy = Human1.y; 214 Human1.oldx = Human1.x; 215 MoveHumanRight(Human1); 216 } 217 else 218 { 219 Human1.y = Human1.oldy; 220 Human1.x = Human1.oldx; 221 } 222 } 223 } 224 //RENDER 225 if(render && al_is_event_queue_empty(event_queue)) 226 { 227 render = false; 228 BoundingBoxes(); 229 230 al_draw_bitmap(Human2.image, Human2.x - Human2.w / 2, Human2.y - Human2.h / 2, 0); 231 al_draw_bitmap(Human1.image, Human1.x - Human1.w / 2, Human1.y - Human1.h / 2, 0); 232 al_flip_display(); 233 al_clear_to_color(al_map_rgb(0, 0, 0)); 234 } 235 } 236 //DESTROY ALLEGRO OBJECTS 237 al_destroy_bitmap(Human1.image); 238 al_destroy_font(font18); 239 al_destroy_timer(timer); 240 al_destroy_event_queue(event_queue); 241 al_destroy_display(display); 242 243 return 0; 244} 245void MoveHumanUp(Player &Human1) 246{//Movement 247 Human1.y -= Human1.speed; 248 if (Human1.y < 0) 249 Human1.y = 0; 250} 251void MoveHumanDown(Player &Human1) 252{//Movement 253 Human1.y += Human1.speed; 254 if (Human1.y > HEIGHT) 255 Human1.y = HEIGHT; 256} 257void MoveHumanLeft(Player &Human1) 258{//Movement 259 Human1.x -= Human1.speed; 260 if (Human1.x < 0) 261 Human1.x = 0; 262} 263void MoveHumanRight(Player &Human1) 264{//Movement 265 Human1.x += Human1.speed; 266 if (Human1.x > WIDTH) 267 Human1.x = WIDTH; 268} 269void BoundingBoxes() 270{ 271 //Only here to draw rectangles on screen to get co-ords (Will be removed once im done) 272 273 //Top Mountains 274 al_draw_filled_rectangle(0, 0, 860, 90, al_map_rgba(255, 0, 255, 100)); 275 al_draw_filled_rectangle(920, 0, 1280, 210, al_map_rgba(255, 0, 255, 100)); 276 al_draw_filled_rectangle(1160, 210, 1280, 280, al_map_rgba(255, 0, 255, 100)); 277} 278void TestBounds(Bounds b2[], int size) 279{ //Give the x,y,w,h values 280 for (int i = 0; i < size; i++) 281 { 282 b2[0].x = 0; 283 b2[0].y = 0; 284 b2[0].w = 860; 285 b2[0].h = 90; 286 287 b2[1].x = 920; 288 b2[1].y = 0; 289 b2[1].w = 360; 290 b2[1].h = 210; 291 } 292} 293bool Collision2(Player &Human1, Bounds b2[], int size) 294{//Test for collision 295 296 for (int i = 0; i < size; i++) 297 { 298 if (Human1.x > Human1.speed + b2[i].x + b2[i].w - 1) 299 return false; 300 if (Human1.y > Human1.speed + b2[i].y + b2[i].h - 1) 301 return false; 302 if (b2[i].x > Human1.x + Human1.speed + Human1.w - 1) 303 return false; 304 if (b2[i].y > Human1.y + Human1.speed + Human1.h - 1) 305 return false; 306 else 307 return true; 308 } 309}

@ Chris
I used (Changed your code to match mine)

#SelectExpand
1void TestBounds(Bounds b2[], int size) 2{ 3 for (int i = 0; i < size; i++) 4 { 5 b2[i].x = 0; //note i 6 b2[i].y = 0; 7 b2[i].w = 860; 8 b2[i].h = 90; 9 10 } 11}

But this didn't seem to allow any collision, probably my coding somewhere, it always is.
And again I really appreciate all your help guys, it is helping me learn, albeit slow at times.

Edgar Reynaldo
Major Reynaldo
May 2007
avatar

Allow me to explain in code :

#SelectExpand
1typedef struct Rectangle { 2 int x,y,w,h; 3}; 4 5void RectSet(Rectangle* r , int x , int y , int w , int h) { 6 r->x = x; 7 r->y = y; 8 r->w = w; 9 r->h = h; 10} 11 12void RectInit(Rectangle* r) { 13 RectSet(r , 0 , 0 , 0 , 0); 14} 15 16bool RectanglesCollide(Rectangle* r1 , Rectangle* r2) { 17 return (! ( ((r2->x + r2->w) < r1->x) && // check r2 right x against r1 left x 18 ((r1->x + r1->w) < r2->x) && // check r1 right x against r2 left x 19 ((r2->y + r2->h) < r1->y) && // check r2 bottom y against r1 top y 20 ((r1->y + r1->h) < r2->y) ));// check r1 bottom y against r2 top y 21}

Then you include a Rectangle member in your Human or other structs if they need one.

typedef struct Human {
   // ...
   Rectangle pos;
};

And you test for collision between Humans and other objects (such as Humans) by checking their rectangles.

bool HumansCollide(Human* h1 , Human* h2) {
   return RectCollide(&(h1->pos) , &(h2->pos));
}

Lasaqus7
Member #15,950
May 2015

You certainly seem to know what your talking about Edgar in the way you respond with your coding, although at times I don't understand your code especially for this newbie (Bearing in mind my C++/Allegro5 knowledge was none existent a month - 6 weeks ago and I only watched Mike Geigs tutorials and some of CodingMadeEasy).

If I do get what you mean, should it be like this for me ? I'm trying not to change too much as I need to be able to take this code and put it straight into my game's code.

#SelectExpand
1typedef struct Bounds //Not sure what typedef is for :-( 2{ 3 int x, y, w, h; 4}; 5typedef struct Player 6{ 7//Other Player things in here too 8 Bounds* b2; 9}; 10Bounds* b2; //Not sure if I still need this, I think I do 11int main 12{//Example piece of code to show how I call the function in main. 13 else if (ev.type == ALLEGRO_EVENT_TIMER) 14 { 15 render = true; 16 TestBounds(*b2); 17 18 if (keys[W]) 19 { 20 if (Collision2(Human1, *b2) == false) 21 { 22 Human1.oldy = Human1.y; 23 Human1.oldx = Human1.x; 24 MoveHumanUp(Human1); 25 } 26 else 27 { 28 Human1.y = Human1.oldy; 29 Human1.x = Human1.oldx; 30 } 31 } 32} 33void BoundsSet(Bounds* b2, int x, int y, int w, int h) 34{ 35 b2->x = x; // Not sure what ->x means either, im guessing the value b2 has stored for x. 36 b2->y = y; 37 b2->w = w; 38 b2->h = h; 39} 40void TestBounds(Bounds* b2) 41{ 42 BoundsSet(b2, 0, 0, 860, 90); 43 BoundsSet(b2, 920, 0, 360, 210); 44} 45bool Collision2(Player &Human1, Bounds* b2) 46{ 47 if (Human1.x > Human1.speed + b2->x + b2->w - 1) 48 return false; 49 if (Human1.y > Human1.speed + b2->y + b2->h - 1) 50 return false; 51 if (b2->x > Human1.x + Human1.speed + Human1.w - 1) 52 return false; 53 if (b2->y > Human1.y + Human1.speed + Human1.h - 1) 54 return false; 55 else 56 return true; 57}

If this is somewhat correct then it breaks at my BoundsSet function @ b2->x = x; saying Unhandled exception at 0x01352644 in Testing.exe: Access violation writing location 0x00000000.

I think I have all your code in place, and again I'm sorry if I don't fully grasp everything you helping me on.

Edgar Reynaldo
Major Reynaldo
May 2007
avatar

Take a look at the highlighted lines (which have problems) and the comments I made

#SelectExpand
1typedef struct Player { 2 Bounds* b2; 3}; 4 5// This is shadowed (has the same name as another variable) 6// and uninitialized (you never give it a value)
7Bounds* b2;
8 9Player p1;// our bounds is inside the player struct 10 11//... 12 13//Example piece of code to show how I call the function in main. 14else if (ev.type == ALLEGRO_EVENT_TIMER) { 15 render = true; 16 17 // This is unitialized. Accessing it will crash
18 TestBounds(*b2);
19 20 // This is what you want 21 TestBounds(p1->b2);

Please note that this code still has problems. p1->b2 is still unitialized. You never allocate memory for the Bounds struct. What you want is probably a member variable for Bounds that isn't a pointer.

typedef struct Player {
   Bounds b;
};
TestBounds(&(p1->b));

Lasaqus7
Member #15,950
May 2015

Sorry, just not getting this, i just keep getting faced with errors

#SelectExpand
1typedef struct Bounds 2{ 3 int x, y, w, h; 4}; 5typedef struct Player 6{ 7 int ID; 8 int x; 9 int y; 10 int bx; 11 int by; 12 int oldx; 13 int oldy; 14 int w = 48; 15 int h = 48; 16 int speed = 4; 17 Bounds b; 18 Bounds* b2; 19 ALLEGRO_BITMAP *image; 20}; 21Player Human1; 22 23void BoundsSet(Bounds* b2, int x, int y, int w, int h); 24void TestBounds(Bounds* b2); 25bool Collision2(Player &Human1, Bounds b2, int size); 26 27int main 28{ 29else if (ev.type == ALLEGRO_EVENT_TIMER) 30 { 31 render = true; 32 TestBounds(&(Human1->b)); //Error Player Human1 Error: expression must have a pointer type. 33 34 if (keys[W]) 35 { 36 //if (Collision(Human1, Human2) == false) 37 if (Collision2(Human1, &(Human1->b)) == false)//Error Player Human1 Error: expression must have a pointer type 38 { 39 Human1.oldy = Human1.y; 40 Human1.oldx = Human1.x; 41 MoveHumanUp(Human1); 42 } 43 else 44 { 45 Human1.y = Human1.oldy; 46 Human1.x = Human1.oldx; 47 } 48 } 49} 50void BoundsSet(Bounds* b2, int x, int y, int w, int h) 51{ 52 b2->x = x; 53 b2->y = y; 54 b2->w = w; 55 b2->h = h; 56} 57void TestBounds(Bounds* b2) 58{ 59 BoundsSet(b2, 0, 0, 860, 90); 60 BoundsSet(b2, 920, 0, 360, 210); 61} 62bool Collision2(Player &Human1, Player* b2) 63{ 64 if (Human1.x > Human1.speed + b2->x + b2->w - 1) 65 return false; 66 if (Human1.y > Human1.speed + b2->y + b2->h - 1) 67 return false; 68 if (b2->x > Human1.x + Human1.speed + Human1.w - 1) 69 return false; 70 if (b2->y > Human1.y + Human1.speed + Human1.h - 1) 71 return false; 72 else 73 return true; 74}

I though I nearly had it when I did it earlier which was this way, but I'm guessing this way can't work which is a shame

#SelectExpand
1void TestBounds(Bounds b2[], int size) 2{ //Give the x,y,w,h values 3 for (int i = 0; i < size; i++) 4 { 5 b2[0].x = 0; 6 b2[0].y = 0; 7 b2[0].w = 860; 8 b2[0].h = 90; 9 10 b2[1].x = 920; 11 b2[1].y = 0; 12 b2[1].w = 360; 13 b2[1].h = 210; 14 } 15}

I think the problem I am having it that I don't fully understand the setup up between having a struct then calling information from it inside another struct and linking everything together and I only have 2 days left before I finish the game:-(

Edgar Reynaldo
Major Reynaldo
May 2007
avatar

What you really need is a primer on C.

Given this declaration :

struct Rectangle {
  int x,y,w,h;
};
struct Player {
   Rectangle r;
};

You can create an array of one of them like this :

const int array_size = 4;

Rectangle rects[array_size] = {
   {0,0,10,10},
   {0,0,10,10},
   {0,0,10,10},
   {0,0,10,10}
};

You don't have to specify a size in this case but it never hurts to specify it directly.

You access data members either through the struct or through a pointer.

Rectangle r = {0,0,10,10};
Rectangle* prect = &r;

r.x = 5;
prect->y = 15;

And when you have nested structs you follow from outer to inner members :

Player p = {{0,0,10,10}};
Player* pplayer = &p;

p.r.x = 10;
pplayer->r.y = 20;

And you'll need a for loop as well :

for (int i = 0 ; i < array_size ; ++v) {

}

It has the syntax :

for (VAR v = initial_value ; v comparison_operator v2 ; v expression) {

And finally, you access array elements using an index ie array[i] where i is from 0 to size - 1. It is zero indexed.

Lasaqus7
Member #15,950
May 2015

I'm trying to work through you code again Edgar, and I appreciate your time on this

Taking things 1 step at a time

If I do

#SelectExpand
1struct Rectangle { 2 int x,y,w,h; 3}; 4struct Player { 5 Rectangle r; 6//Rectangle errors with:- BOOL_stdcall Rectangle(HDC hdc, int left, int top, int right, int bottom)Error:function "Rectangle" is not a type name 7};

I read into this and 1 person fixed it they said
They added:
#using <system.drawing.dll>
at the top
However if I add this I get : Error:"#using" requires C++/CLI mode

Edgar Reynaldo
Major Reynaldo
May 2007
avatar

Lasaqus7 said:

I read into this and 1 person fixed it they said
They added:
#using <system.drawing.dll>
at the top
However if I add this I get : Error:"#using" requires C++/CLI mode

That is not how you fix it. :/

The error has to do with Microsoft so helpfully defining a function named Rectangle which conflicts with naming the struct Rectangle as well. If you #define NOGDI before including windows headers then the Rectangle function won't be visible.

Try this code with and without NOGDI defined :

#SelectExpand
1 2 3 4#define NOGDI 5#include "windows.h" 6 7struct Rectangle { 8 int x,y,w,h; 9}; 10 11struct Player { 12 Rectangle r; 13}; 14 15int main() { 16 17 Player p; 18 Player* pp = &p; 19 Rectangle* pr = &(pp->r); 20 21 pr->x = 1; 22 pr->y = 1; 23 24 p.r.w = 10; 25 p.r.h = 10; 26 27 return 0; 28}

You should get comfortable working with both member access using the dot operator . and dereferencing pointers with the dereference operator ->. & means "the address of" an object, and is what a pointer stores. That's why I set pp to &p in the code above. It now holds the value of the address of the Player object p. And you can also dereference a pointer with *. *pointer means "the data pointed to by" pointer. And so to access the Player p through pp you would use *pp = Player object.

 1   2 


Go to: