Collision Detection: Difference between revisions

From Arduboy Wiki
Jump to navigation Jump to search
 
(4 intermediate revisions by the same user not shown)
Line 1: Line 1:


=== Background ===
The Arduboy2 library provides functions to detect when two rectangles or a rectangle and a point overlap.  It is implemented in such a way that is independent of other constructs such as the screen and sprites.  In practice though, the detection of collisions is often focused on the collision of sprites (players, bullets, etc) with other entities (enemy sprites, background elements or other play elements).  These collisions may occur onscreen or off and functions within the library are flexible enough to cater for this.
The Arduboy2 library provides functions to detect when two rectangles or a rectangle and a point overlap.  It is implemented in such a way that is independent of other constructs such as the screen and sprites.  In practice though, the detection of collisions is often focused on the collision of sprites (players, bullets, etc) with other entities (enemy sprites, background elements or other play elements).  These collisions may occur onscreen or off and functions within the library are flexible enough to cater for this.


The following examples and code assume you are familiar with the concept and functions available to render sprites.  More information on this can be found in the article covering the [[Sprites / SpritesB Library]].
The following examples and code assume you are familiar with the concept and functions available to render sprites.  More information on this can be found in the article covering the [[Sprites / SpritesB Library]].


=== But how does the collision detection work? ===
== But how does the collision detection work? ==
 
The Arduboy2 library provides two collision detection methods which are shown below.  <pre>
The Arduboy2 library provides two collision detection methods which are shown below.  <pre>
bool collide(Point point, Rect rect);
bool collide(Point point, Rect rect);
Line 47: Line 47:
</pre>
</pre>


Running the above code will print 1 as the point at position 5, 5 is in the centre of the square whose top left corner is at position 2, 2 and lower right corner is at 11, 11. 


Running the above code will print 1 as the point at position 5, 5 is in the centre of the square whose top left corner is at position 2, 2 and lower right corner is at 11, 11.
Moving the point outside of the square with the following code:
[[File:Collision 01.png|thumb]]
[[File:Collision 01.png|thumb]]
<pre>
<pre>
Line 55: Line 56:


Serial.println(arduboy.collision(p, r));     
Serial.println(arduboy.collision(p, r));     




Line 64: Line 67:


Will print 0 as the point at position 14, 2 is outside of the square whose top left corner is at position 5, 5 and lower right corner is at 15, 15.
Will print 0 as the point at position 14, 2 is outside of the square whose top left corner is at position 5, 5 and lower right corner is at 15, 15.


The second function compares the position of two rectangles and tests to see if there is any overlap.  This is demonstrated in the code below:
The second function compares the position of two rectangles and tests to see if there is any overlap.  This is demonstrated in the code below:
 
[[File:Collision 03.png|thumb]]
<pre>
<pre>
Rect r1 = {12, 8, 16, 16};
Rect r1 = {2, 2, 5, 5};
Rect r2 = {16, 20, 16, 16};
Rect r2 = {5, 5, 7, 7};


Serial.println(arduboy.collision(r1, r2));
Serial.println(arduboy.collision(r1, r2));
</pre>
</pre>


Will print 1 as the two rectangles overlap.  The first rectangle’s top left corner is at 12, 8 and its lower right corner is at 28, 24. The second rectangle’s top left corner is at 16, 20 and its lower left corner is at 32, 36.  
Running the above code will print 1 as the two rectangles overlap.  The first rectangle’s top left corner is at 2, 2 and its lower right corner is at 6, 6. The second rectangle’s top left corner is at 5, 5 and its lower left corner is at 11, 11.
 
Moving the smaller rectangle away from the larger ones so they do not overlap:
 
[[File:Collision 04.png|thumb]]


<pre>
<pre>
r1.x = 40;
r1.x = 0;
r1.y = 40;
r1.y = 0;


Serial.println(arduboy.collision(r1, r2))
Serial.println(arduboy.collision(r1, r2))
</pre>
</pre>


Will print 0 as the two rectangles do not overlap anymore. The first rectangle’s top left corner is now at 40, 40 and its lower right corner is at 56, 56. The second rectangle’s top left corner is at 16, 20 and its lower left corner is at 32, 36.
Running the above code will print 0 as the two rectangles do not overlap anymore. The first rectangle’s top left corner is now at 0, 0 and its lower right corner is at 4, 4. The second rectangle’s top left corner is at 5, 5 and its lower left corner is at 11, 11.
 
== Using Collision Detection with Sprites ==
 


One (minor) issue with the detection code is that when detecting collisions, you need to construct the ‘rectangle’ and ‘point’ structures based on the positions of the players and obstacles in the game.   
It is common to want to detect collisions between sprites - such as a player and an enemy.  This often results in code like the following being scattered through a game's code to create Rects with the same position, width and height as the player and enemy sprites.   


<pre>
<pre>
Serial.print("A collides with B : ");
Rect player = { player.x, player.y, 16, 16 };
Serial.println(arduboy.collide( (Rect){xA, yA, 16, 16}, (Rect){xB, yB, 16, 16} ));
Rect enemy =  { enemy.x, enemy.y, 16, 16 };
 
Serial.println(arduboy.collide(player, enemy);
</pre>
</pre>


In the sample code aboce, two rectangle structures are populated with the current positions of the moving squares and the image size (16 x 16 pixels).  This is relatively simple when there are only a few objects to consider but can become a little more complex and error prone if there are numerous objects with different sizes.
This is perfectly acceptable when there are only a handful of sprites to track - but what if there were dozens, and their sizes all different?  The chance of coding errors increases significantly!


A typical design pattern for action games is to define a class to hold information about players including their current position.  A sample header and implementation class is shown below
Assuming you are using classes to represent your players, enemies and other game play elements, a typical design pattern for action games is to define a class to hold information about players including their current position.  A sample header and implementation class is shown below


<pre>
<pre>
Line 127: Line 154:
</pre>
</pre>


The header declares three public variables for the player’s coordinates and the image used when rendering the screen.  It also prototypes a function that will return a rectangle structure that will describe the area on screen that the player currently occupies.  The implementation of that function is included in the .cpp file.  It simply creates and returns a populated Rect structure using the ‘x’ and ‘y’ coordinates from the class itself and the width and height of the image by reading these from the image array itself.
The header declares three public variables for the player’s coordinates and the image used when rendering the screen.  It also prototypes a function that will return a rectangle structure that will describe the area on screen that the player currently occupies.  The implementation of that function is included in the .cpp file.  It simply creates and returns a populated Rect structure using the ‘x’ and ‘y’ coordinates from the class itself and the width and height of the image by reading these from the image array itself which happen to be the first two bytes when using the standard Sprites library. If you are unfamiliar with how sprite data is stored refer [[Sprites / SpritesB Library|here]].


The code below shows the declaration of two player objects and shows how they can be used when testing collisions.  
The code below shows the declaration of two player objects and shows how they can be used when testing collisions.  


<pre>
<pre>
Line 155: Line 181:


https://github.com/filmote/Collide_Article1  (simple example)
https://github.com/filmote/Collide_Article1  (simple example)
https://github.com/filmote/Collide_Article2  (using the ‘Player’ class).
https://github.com/filmote/Collide_Article2  (using the ‘Player’ class).

Latest revision as of 04:59, 16 August 2024

The Arduboy2 library provides functions to detect when two rectangles or a rectangle and a point overlap. It is implemented in such a way that is independent of other constructs such as the screen and sprites. In practice though, the detection of collisions is often focused on the collision of sprites (players, bullets, etc) with other entities (enemy sprites, background elements or other play elements). These collisions may occur onscreen or off and functions within the library are flexible enough to cater for this.

The following examples and code assume you are familiar with the concept and functions available to render sprites. More information on this can be found in the article covering the Sprites / SpritesB Library.

But how does the collision detection work?

The Arduboy2 library provides two collision detection methods which are shown below.

bool collide(Point point, Rect rect);
bool collide(Rect rect1, Rect rect2);

It also provides two structures to assist.

struct Rect {
  int16_t x;
  int16_t y;
  uint8_t width;
  uint8_t height; 
};

struct Point {
  int16_t x; 
  int16_t y; 
};

The structures describe a rectangle and a point in space - the x / y coordinates are arbitrary and their origin can be anywhere but it typical to align these with screen's coordinates. Thus 0, 0 is located at the top-left corner of the screen with negative x and y values representing locations to the left and above the screen respectively.

The first function tests to see if a point is contained within the specified rectangle. The sample code below demonstrates this:

Point p;
Rect r;

p.x = 5;
p.y = 5;

r.x = 2;
r.y = 2;
r.width = 10;
r.height = 10;

Serial.println(arduboy.collision(p, r));    

Running the above code will print 1 as the point at position 5, 5 is in the centre of the square whose top left corner is at position 2, 2 and lower right corner is at 11, 11.

Moving the point outside of the square with the following code:

p.x = 14;
p.y = 2;

Serial.println(arduboy.collision(p, r));    








Will print 0 as the point at position 14, 2 is outside of the square whose top left corner is at position 5, 5 and lower right corner is at 15, 15.

The second function compares the position of two rectangles and tests to see if there is any overlap. This is demonstrated in the code below:

Rect r1 = {2, 2, 5, 5};
Rect r2 = {5, 5, 7, 7};

Serial.println(arduboy.collision(r1, r2));








Running the above code will print 1 as the two rectangles overlap. The first rectangle’s top left corner is at 2, 2 and its lower right corner is at 6, 6. The second rectangle’s top left corner is at 5, 5 and its lower left corner is at 11, 11.

Moving the smaller rectangle away from the larger ones so they do not overlap:

r1.x = 0;
r1.y = 0;

Serial.println(arduboy.collision(r1, r2))








Running the above code will print 0 as the two rectangles do not overlap anymore. The first rectangle’s top left corner is now at 0, 0 and its lower right corner is at 4, 4. The second rectangle’s top left corner is at 5, 5 and its lower left corner is at 11, 11.

Using Collision Detection with Sprites

It is common to want to detect collisions between sprites - such as a player and an enemy. This often results in code like the following being scattered through a game's code to create Rects with the same position, width and height as the player and enemy sprites.

Rect player = { player.x, player.y, 16, 16 };
Rect enemy =  { enemy.x, enemy.y, 16, 16 };

Serial.println(arduboy.collide(player, enemy);

This is perfectly acceptable when there are only a handful of sprites to track - but what if there were dozens, and their sizes all different? The chance of coding errors increases significantly!

Assuming you are using classes to represent your players, enemies and other game play elements, a typical design pattern for action games is to define a class to hold information about players including their current position. A sample header and implementation class is shown below

Player.h:

#include "Arduboy2.h"

class Player {

   public:
     Rect getRect();
     
   int16_t x;
   int16_t y;
   const uint8_t *bitmap;
   
};



Player.cpp:

#include "Player.h"
#include "Arduboy2.h"

Rect Player::getRect() {
   
  return (Rect){x, y, pgm_read_byte(bitmap), pgm_read_byte(bitmap + 1)};

}

The header declares three public variables for the player’s coordinates and the image used when rendering the screen. It also prototypes a function that will return a rectangle structure that will describe the area on screen that the player currently occupies. The implementation of that function is included in the .cpp file. It simply creates and returns a populated Rect structure using the ‘x’ and ‘y’ coordinates from the class itself and the width and height of the image by reading these from the image array itself which happen to be the first two bytes when using the standard Sprites library. If you are unfamiliar with how sprite data is stored refer here.

The code below shows the declaration of two player objects and shows how they can be used when testing collisions.

Player playerA;
Player playerB;

playerA.x = 20;
playerA.y = 12;
playerA.bitmap = squareA;

playerB.x = 100;
playerB.y = 24;
playerB.bitmap = squareB;

...

Serial.print("A collides with B : ");
Serial.println(arduboy.collide(playerA.getRect(), playerB.getRect()));

In my opinion, this approach results in much more readable code.

The simple collision application and the ‘advanced’ version using a ‘Player’ class can be found in my repository.

https://github.com/filmote/Collide_Article1 (simple example)

https://github.com/filmote/Collide_Article2 (using the ‘Player’ class).