| RHS => Allan => Programming 2 => Notes => How to detect collisions... |
Updated:
02/02/2004 23:36 WebMaster: allanhn@rhs.dk |
How to
detect collisions
between moving GUI-objects?
In a GUI with moving objects (e.g. animation) it's sometimes nice to know when two (or more) objects are colliding - this note describes a solution.
|
The solution is used in the applet you (hopefully) see to the right (we'll discuss the applet a little later). Two types of objects (circles and planets) are moving (and bouncing) inside a rectangle with individual direction (angle) and speed. When a circle is "touching" a planet they both change color. In this applet a collision is defined as "the areas of the two objects are overlapping". In other situations the definition could be different (maybe overlapping isn't possible) - that's not important here. You can define your own rules and construct your program so it reacts accordingly. The main problem is to know how you can detect that objects are colliding! First let me admit that the solution isn't perfect!! In defining the
objects area I'm cheating a bit. It turns out that for complex shapes
it's VERY difficult to detect a collision. |
|
Instead of comparing complex shapes we'll compare rectangles. The trick is
that we define the smallest rectangle that covers the shape completely (or partial
if that's more suitable).
In other words this area will be the bounds for our shape. So instead
of checking:
we'll
be checking:
- much easier!
We loose a little accuracy, but you'll normally not see the difference when
the shapes are moving.
To check whether two rectangles overlap we have to compare their x-coordinates
and their y-coordinates.
For the sake of simplicity we'll assume that the y-coordinates of the two rectangles
DO overlap. So for the time being we'll only focus on the x-coordinates.
Ignoring the y-coordinates we have five different possibilities:
| Example 1 | Example 2 | Example 3 | Example 4 | Example 5 |
![]() |
![]() |
![]() |
![]() |
![]() |
Let's name the left and right sides of Box A as A_Left and A_Right.
For Box B we name the sides as B_Left and B_Right.
For a collision to occur the following statement must be true:
| (A_Left is to the left of B_Right) AND (A_Right is to the right of B_Left) | ||
| (To be absolutely precise we should say: "...to the left of or equal to..." etc.) |
Note that the statement consists of two parts which both must be true!
The first part excludes "Example 5"
and the second part excludes "Example 1" leaving us with the three examples
where collision do occur (as we wanted!)..
Take your time to ensure yourself that the statement implies collision and, importantly: collision implies that the statement is true!
Now if we name the x-coordinate for the side A_Left as AX1, the x-coordinate for the side A_Right as AX2 and so on, we can translate the statement (using Java code) into :
(AX1 <= BX2) && (AX2 >= BX1)
It's possible to do exactly the same considerations for the y-coordinates.
NOTE that on a GUI the y-coordinate's starts from the top (y = 0) and
increases as we go down the screen (in contrast to "normal" coordinates).
So we end up with: (AY1 <= BY2) && (AY2 >= BY1).
Of course both the x-coordinate and the y-coordinate must overlap so the final statement is:
| (AX1 <= BX2) && (AX2 >= BX1) && (AY1 <= BY2) && (AY2 >= BY1) | |||
| If you're not sure what's going on, then draw some examples with and some without overlap, then check the x- and y-coordinates against the statement. | ![]() |
||
Now back to the applet (which, btw, can run as an application
also).
Below is a description of each file.
I've tried to apply "quality" as much as possible (at least for the
model-classes) - see comments below. The model-view-controller (MVC) architecture
is used.
| MovingPoint.java | Defining a class describing an "invisible"
moving point. Contains modifiers for all "moving attributes":
position (x- and y-coordinates), direction (angle for movement)
and speed (distance moved in each time-period). Also contains a bounding rectangle (see above). Implemented with "Effective Java" in mind! |
| MovingCircle.java | Sub-class of MovingPoint.
Adds a radius and a color (with modifiers). Implemented with "Effective Java" in mind! |
| MovingImage.java | Subclass of MovingPoint.
Adds an image (with modifiers). Implemented with "Effective Java" in mind! |
| GameDefaults.java | Holds all the default-values like
width and height of the field, the number of circles and images, the color
of a "normal" circle and of a circle participating in a collision
etc. etc. Look for yourself - the attribute names should be self-explaining! One trick, though: The images must be loaded completely in order to determine their width and height (bounds). As the load-procedure uses non-static Java-methods it must be performed in a non-static context. My solution is to do the image-loading in the constructor. As the class has no methods (only final values) there is no reason for creating an instance of the class (in the same way as you don't instantiate the Math-class). But because of the loading in the constructor it is very important to create an instance (which can be abandoned afterwards) before reading the image-data! Any "cleaner" solutions out there?? |
| GameModel.java | The model holds an array of circles
and an array of images. When initializing objects, the common attributes
are set (with random values) in a method handling MovingPoint's.
As the model must notify the view when it should be updated there's a method
to be called from anyone who changes the state of the model. Note that the array's can be accessed directly via two methods getXxx() |
| GameView.java | The view knows how to visualize the objects. Holds a reference to the model so it can get access to the two array's. |
| GameController.java | The controller is responsible for
updating the movements of every object. Using a bit of math the new positions
are calculated. Then they're checked against the global borders and corrected
if an object tries to get out of the game.... When an object hits the borders, the objects "bounce" back - so the angle is also checked and maybe corrected. And finally we check for collisions. First the appearance of every object is set to normal (so objects who just left a collision looks as they should) and then every image is checked against every circle for collision (objects don't change if they collide with an object of the same type). If two objects collide their appearance is changed (according to values from GameDefaults). The last thing done by the controller is to notify the model that it's state has changed. |
| CollisionExample.java | Ties everything together. Note that there's a main()-method so the program can be executed as an application and there's init(), start() and stop() methods so it can run as an applet. Both ways sets up the game through a common method. The controller is "powered" by a Timer (defining the "time-slices" between every movement) |
| Earth1.gif, Earth2.gif | Images for a "normal" earth and a "collided" earth. |
In this web-page all files has been packed into one .jar file
Particularly the model-classes are more "heavyweight" than
necessary for this example.
They're constructed according to the recommendations in "Effective Java",
in the other classes you'll see less dogmatic programming... ![]()
The following items have been considered (more or less..) during the process
: 1, 2, 3, 4, 7, 8, 9, 10, 11, 12, 13, 14, 15, 19(!), 23, 24, 25, 26, 29, 30,
37, 38, and 39.
Note that I wrote considered - sometimes this actually means that after considering, I choose to go against the recommendations! I also did that a few times without any considering......
Have fun finding all my mistakes!
If you find a lot I'll consider rewriting the program....
- Allan