Showing posts with label collision detection. Show all posts
Showing posts with label collision detection. Show all posts

Sunday, August 22, 2010

More Collision Detection: Bouncing the Right Way

A common result of a collision between two objects in a video game is a bounce. To do this right, we have to know which direction to make things bounce. Here's one way to do that easily in Java. I've got a ball that's going to bounce in one of eight directions depending on how it hits a brick.

Diagram showing the results of a ball hitting a brick and bouncing, depending on how it strikes the brick. Collision detection in action.

I've marked the changes to the ball's velocity that will occur as a result of each possible type of collision. The three arrows on the left side all show that the xVel (x velocity) is a negative value, the three arrows on the bottom all show that yVel (y velocity) should become a positive value.

In the cases of the corners, both the x and y velocities are set. The bounces off the middles of the sides all cause one velocity to be set, but the other is left alone. For example, a bounce off the right side of the brick sets the xVel to a positive value, but the yVel is unchanged. So if yVel was a positive value before (meaning the ball is travelling down on screen) then it will keep travelling down. If it was negative--moving up--then it will keep moving up. Only the x direction of movement will change. It will start moving from left to right instead of right to left.

It's really easy to detect a collision, but now we need to know the relationship between the two colliding objects so that we can make the ball bounce in the correct direction. As we learned in Stopping Jonathan Livingston Seagull, the effect of how much we move an object before we check a collision can cause problems if we don't understand the limits of how we detect them.

In this program, our ball's movement is presently set at one quarter of its width. We could go as high as one half its width and still have our collision detection routines work properly here. Fortunately, we want to keep the distance the ball moves each turn short to make the movement look smoother.

When we hit the brick, we want to see where it hits the ball. This will tell the ball which way to bounce, just like a real ball. In the drawing above, we have a red brick on a cyan (blue-green) playfield. The ball is dark blue. Where the ball is intersecting, or overlapping, the brick is shown in yellow. Now we just need to know which side of the ball is getting "pushed in" this way. Then we bounce the opposite way.

Here's the program, the key code we're talking about is highlighted:
/* A simple video game style kernel, revision 4.
by Mark Graybill, August 2010
Has a ball bouncing off a brick.
Because the ball moves at a fixed speed,
slow enough we won't miss a collision,
we can use simple colliision detection.

Uses the Brick class from the article
"Multiple Constructor Methods"
*/

import java.util.*;
import java.awt.*;
import javax.swing.*;
import java.lang.Math;

public class VidBrick extends JPanel{

public Rectangle screen, bounds; // The screen area and boundary.
public JFrame frame; // A JFrame to put the graphics into.
public VGTimerTask vgTask; // The TimerTask that runs the game.
public VGBall ball; // The game ball, a subclass of Rectangle.
private Brick brick; // A brick for the ball to interact with.

/* This sets up the screen area, and creates instances of
the JFrame, VGBall, VGTimerTask, etc that we'll need. */
public VidBrick(){
super();
screen = new Rectangle(0, 0, 600, 400);
bounds = new Rectangle(0, 0, 600, 400); // Give some temporary values.
ball = new VGBall();
frame = new JFrame("VidBrick");
vgTask = new VGTimerTask();
brick = new Brick();
}

class VGTimerTask extends TimerTask{
public void run(){
ball.move();
frame.repaint();
}
}

class VGBall extends Rectangle{
int xVel, yVel; // The ball's velocity.
Color ballColor; // The color of the ball.

/* Create a VGBall with default location of upper left
corner, size of 20x20 pixels, moving at one quarter
its height and width per turn--plus it's blue. */
public VGBall(){
super(0, 0, 20, 20);
xVel = width/4;
yVel = height/4;
ballColor=new Color(0, 0, 128);
}

/* Move the Ball. */
public void move(){
// Move the ball according to the game rules.
x+=xVel; // Move horizontally.
y+=yVel; // Move vertically.

// Detect edges and bounce if necessary.
if (x > (bounds.width - width)){
xVel = -xVel; // reverse movement.
x = bounds.width - width; // Set location to screen edge.
}
if (y > (bounds.height - height)){
yVel = -yVel; // reverse movement.
y = bounds.height - height;
}
if (x <= 0) { xVel = -xVel; x = 0; }
if (y <= 0) { yVel = -yVel; y = 0; }

// Detect Brick and bounce if necessary.
if (intersects(brick)) {
// Get the intersection rectangle to find out which way to bounce.
Rectangle iRect = intersection(brick);
// If we hit on the left side, go left (negative x velocity).
if ((x+(width/2))<(iRect.x+(iRect.width/2))){xVel=(0-Math.abs(xVel));}
// If we hit on the right side, go right (positive x velocity).
if ((x+(width/2))>(iRect.x+(iRect.width/2))){xVel=Math.abs(xVel);}
// If we hit on the top, go up.
if ((y+(height/2))<(iRect.y+(iRect.height/2))){yVel=(0-Math.abs(yVel));}
// If we hit on the bottom, go down.
if ((y+(height/2))>(iRect.y+(iRect.height/2))){yVel=Math.abs(yVel);}
} // if intersects
}

/* Draw the ball into the provided graphics context. Preserves
the context's drawing color setting. */

public void draw(Graphics g){
// the ball draws itself in the graphics context given.
Color gcColor = g.getColor(); // Preserve the present color.
g.setColor(ballColor); // Use the ball's color for the ball.
g.fillRect(x, y, width, height); // Draw the ball.
g.setColor(gcColor); // Restore prior color.
} // end draw()
} // end of class VGBall

public void paintComponent(Graphics g){
// Get the drawing area bounds for game logic.
bounds = g.getClipBounds();
// Clear the drawing area.
g.clearRect(screen.x, screen.y, screen.width, screen.height);
// Draw the brick.
g.setColor(brick.getColor());
g.fillRect(brick.x, brick.y, brick.width, brick.height);
// Draw the ball.
ball.draw(g);
}

/* Main program loop. */
public static void main(String arg[]){

java.util.Timer vgTimer = new java.util.Timer(); // Create a Timer object
VidBrick panel = new VidBrick();

panel.frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
panel.frame.setSize(panel.screen.width, panel.screen.height);

panel.frame.setContentPane(panel);
panel.frame.setVisible(true);

// Set up the brick.
panel.brick.x = panel.screen.width/3;
panel.brick.y = panel.screen.height/3;
panel.brick.width = panel.screen.width/3;
panel.brick.height = panel.screen.height/3;

// Set up a timer to do the vgTask regularly.
vgTimer.schedule(panel.vgTask, 0, 33);
}
}

The way we figure out which way to bounce is to find the center of the ball and the center of the intersection rectangle (the yellow box in the picture, where the ball and the brick overlap.) Then if the center of the intersection (shown as an orange dot on the pic) is to the right of the center of the ball (a white dot) we bounce left. If it's to the left, we bounce right, if it's above we bounce down, below we bounce up.

This way we don't have to worry about the size or shape of the brick itself. All we care about is the bit that strikes the ball. Then the ball bounces the correct way.

If we had wanted to get really fancy, we could have made the ball bounce at different angles depending on the angle between the center of the ball and the center of the intersection rectangle. If you're familiar with trigonometry, take a look at the Math class in Java and see if you can implement this.
StumbleUpon

Wednesday, August 18, 2010

Stopping Jonathan Livingston Seagull with Java

In Simple Collision Detection we used the intersects() method of two Rectangle subclasses to see if one object struck another on our playfield. It works, in the example given. But in the example given the velocity of the ball is limited.

Simple collision detection works fine, here.
In the VGBKernel.java example, we have a fairly slow-moving ball detecting collisions with a really large object that's hard to miss.

If we have a brick that is small, and a ball that is fast, though, the ball could pass right over the brick without even realizing it should have collided with it. In other words, if we had a brick wall stretching across the screen, the ball might fly "through" it when we want it to stop or bounce. Let's say we're falling and want to detect a brick that acts as a floor. The floor goes across the screen between heights 100 and 105. The ball starts at location 75 and has a velocity of 50. Its new location would be 125. We move it there, and check for a collision as we did in VGBKernel. No collision. The ball just flew "through" the brick wall like Jonathan Livingston Seagull!

Let's make an example program called BallGrav.java to illustrate the problem. BallGrav.java is a slightly modified version of VGBKernel.java. It uses the same Brick class as we've used before, with no modifications.
/* Based on the video game style kernel, revision 3.
by Mark Graybill, August 2010
Demonstrates collision detection problems, and solutions.
*/

// Import Timer and other useful stuff:
import java.util.*;
// Import the basic graphics classes.
import java.awt.*;
import javax.swing.*;
import java.lang.Math;

public class BallGrav extends JPanel{

public Rectangle screen, bounds; // The screen area and boundary.
public JFrame frame; // A JFrame to put the graphics into.
public VGTimerTask vgTask; // The TimerTask that runs the game.
public VGBall ball; // The game ball, a subclass of Rectangle.
private Brick brick; // A brick for the ball to interact with.

// Create a constructor method:
public BallGrav(){
super();
screen = new Rectangle(0, 0, 600, 400);
bounds = new Rectangle(0, 0, 600, 400); // Give some temporary values.
ball = new VGBall();
frame = new JFrame("BallGrav");
vgTask = new VGTimerTask();
brick = new Brick();
}

// Create an inner TimerTask class that has access to the
// members of the BallGrav.
class VGTimerTask extends TimerTask{
public void run(){
ball.move();
frame.repaint();
}
}

// Create an inner VGBall class that has our game logic in it.
class VGBall extends Rectangle{
int xVel, yVel; // The ball's velocity.
Color ballColor; // The color of the ball.

public VGBall(){
super(300, 0, 20, 20);
xVel = 0; // Start with 0 velocity at center top of screen.
yVel = 0;
ballColor=new Color(0, 0, 128);
}

// Instance methods for VGBall
public void move(){
// Accelerate due to "gravity".
yVel+=3;
// Move the ball according to the game rules.
x+=xVel; // Move horizontally.
y+=yVel; // Move vertically, accelerating as we go.
// Detect edges and stop movement if we hit them.
if (x > (bounds.width - width)){
xVel = 0; // stop movement.
x = bounds.width - width; // Set location to screen edge.
}
if (y > (bounds.height - height)){
yVel = 0; // stop movement.
y = bounds.height - height;
}
if (x <= 0) { xVel = 0; x = 0; }
if (y <= 0) { yVel = 0; y = 0; }

// Check for intersection with Brick,
// change color when touching.
if (intersects(brick)) {
//Stop on top of the brick if we hit it.
yVel = 0;
y = brick.y - height;
}

}

public void draw(Graphics g){
// the ball draws itself in the graphics context given.
Color gcColor = g.getColor(); // Preserve the present color.
g.setColor(ballColor); // Use the ball's color for the ball.
g.fillRect(x, y, width, height); // Draw the ball.
g.setColor(gcColor); // Restore prior color.
} // end draw()

} // end of class VGBall

// Now the instance methods:
public void paintComponent(Graphics g){
// Get the drawing area bounds for game logic.
bounds = g.getClipBounds();
// Clear the drawing area.
g.clearRect(screen.x, screen.y, screen.width, screen.height);
// Draw the brick.
g.setColor(brick.getColor());
g.fillRect(brick.x, brick.y, brick.width, brick.height);
// Draw the ball.
ball.draw(g);
}


public static void main(String arg[]){

java.util.Timer vgTimer = new java.util.Timer(); // Create a Timer object
BallGrav panel = new BallGrav();

panel.frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
panel.frame.setSize(panel.screen.width, panel.screen.height);

panel.frame.setContentPane(panel);
panel.frame.setVisible(true);

// Set up the brick.
panel.brick.x = 0;
panel.brick.y = 4 * (panel.screen.height/5);
panel.brick.width = panel.screen.width;
panel.brick.height = 5;
panel.brick.brickColor = new Color(200,50,50);

// Set up a timer to do the vgTask regularly.
vgTimer.schedule(panel.vgTask, 250, 200);
}
}
This example functions properly. When the ball falls, it strikes the brick floor and stops.

Image of collision detection working.


But let's make the ball accelerate a bit faster. Make the following change in Ball's move() method:
      // Accelerate due to "gravity".
yVel+=5;


Suddenly, the ball doesn't detect the floor any more, and it goes to the bottom of the screen, right through the floor:

Image showing collision detection failure.


Stopping Jonathan Livingston Gameball from Going Through the Wall

What do we do to keep this from happening? Well, one way is to never let the ball move faster than it can detect collisions. Then we'd never let it move more than its own size in any direction. If it's important that it know what side it hits something on (like when we're going to bounce off it), then we may not want to let it go more than half its size in any direction in any one "move." To do this to BallGrav,java, we'd change the code as follows:
      // Accelerate due to "gravity".
yVel+=5;
// Limit our velocity to preserve collisions.
if (yVel>height) { yVel = height; }

Now the ball will never go so fast it never sees the floor.

But what if we want the ball to move faster?

In this case, there are a number of possible solutions. Many of them are fairly complicated, with lots of math or with lots of iteration checking for collisions at each step along the way. Complexity is something we don't want, for a lot of reasons. Here is one possible solution, that uses collision prediction to see if our ball might hit something along the way:

      // Accelerate due to "gravity".
yVel+=5;
// Limit our velocity.
if (yVel>(height*2)) { yVel = height * 2; }
// Move the ball according to the game rules.
// Check for a collision if we're moving fast.
if (yVel>height) {
Rectangle halfway = new Rectangle(x,y+(yVel/2), width, height);
if (halfway.intersects(brick)) { yVel = yVel/2; }
}
x+=xVel; // Move horizontally.
y+=yVel; // Move vertically, accelerating as we go.

Here, I have a velocity limit twice as high as before. This allows the ball to move along a lot faster (before, I could see the ball slow down.) Now, since the ball can move fast enough to fly through things, we first look ahead to see if there's anything in the way. We do this by seeing if there's something halfway there. If so, we cut our original speed in half before we move. Then the collision (if any) happens "naturally."

This is good enough, if the fastest we can move is twice the ball's height. If we allowed three times its height, we might check at points 1/3 and 2/3 of the way along its path.

If we don't want limits like this, we pay the price by having a more complex method to check for collisions. Here, I've limited things to movement in only one direction, up and down. If we're going side to side as well, I'd have to check for waypoints along a diagonal path. To get really precise, I'd have to do something like project lines along the corners of the ball from its current position to its intended new position and see if they intersect anything. When you get into more complex shapes than the rectangles we're using now, you get a different set of choices.

Fortunately, for video games, simple choices are almost always plenty good enough. Checking for collisions at the minimum number of points along the path is almost always good enough. This can be done by dividing the distance to be moved by the largest safe move distance, and checking that many times for collisions along the path.

Avoiding the Problem Entirely

You can also just use a different sort of movement that never has any of the objects moving more than some small number of pixels at a time--small enough that you'll never fail to detect a collision. I'll give an example in a future article, but for now I'll just describe it.

What you do is set a speed at which you move things only, say, one pixel per "move". But you only move them every so often, depending on what their speed is. The fastest objects get moved on every redraw of the screen. Slower objects only get moved on every other update, or every fifth, or something of that sort. That way an object is never moved across the screen by so many pixels at once that it misses seeing a collision with another object.

Collision Detection: Never "Easy"

Collision detection is one of the programmer's bugaboos. It's one of the places where errors are most prone to occur in code, and it can have terrible effects on a game if it doesn't work properly. The programmer's choices are to accept certain limitations or to make more complex code, or to use a technique of object movement that can either be limiting or eat up a lot of processor time.

When my students program their video game projects in class, one of our biggest problems in implementing those games is dealing with collision detection. Even though we're working in Greenfoot, which does a lot of the work for us. We still need to be aware of the limitations built into the system, and work within them.

Now it's your turn. Try reducing the brick in BallGrav.java to a small platform. Now give the ball a constant x velocity. See if you can get the ball to land on that platform reliably, with successful collision detection.
StumbleUpon

Sunday, August 15, 2010

Simple Collision Detection in Java

Collision detection is a basic element of a video game. It is one of the primary means of determining when objects in a game should interact. In Asteroids, there are collisions between the player's shot and the asteroids themselves. There are collisions between the player's shot and the alien spacecraft (hopefully.) There are collisions between the asteroids and the player's ship (argh!) Each of these is critical to the game.

Screen shot of collision detection in the video game kernel, showing color change of ball when intersecting the brick.

Since we don't have actual physics to determine when there's been a collision, as we have in real life, we need to create a set of rules for when a collision has occured then implement those rules in our code. One way to handle collision detection is to see if two potentially colliding objects intersect at all on the playfield. That is, whether one of them overlays the other.

Previously, we have worked with a simple video game kernel, then updated it to make the addition of game logic simpler. We had also created a Brick class that we can use to interact with the ball in our basic video game kernel.

Here is a slightly changed version of the simple video game kernel. It uses the Brick class, so Brick.class should be in the same directory as your VGBKernel.java and VGBKernel.class (it wouldn't be a bad idea to have Brick.java there, too, in case you want to play around with it at all to get different effects with this program through changes of your own.)
/* A simple video game style kernel, revision 3.
by Mark Graybill, August 2010
Uses an inner class to contain game logic,
with another inner class as a game timer.
*/

// Import Timer and other useful stuff:
import java.util.*;
// Import the basic graphics classes.
import java.awt.*;
import javax.swing.*;
import java.lang.Math;

public class VGBKernel extends JPanel{

public Rectangle screen, bounds; // The screen area and boundary.
public JFrame frame; // A JFrame to put the graphics into.
public VGTimerTask vgTask; // The TimerTask that runs the game.
public VGBall ball; // The game ball, a subclass of Rectangle.
private Brick brick; // A brick for the ball to interact with.

// Create a constructor method:
public VGBKernel(){
super();
screen = new Rectangle(0, 0, 600, 400);
bounds = new Rectangle(0, 0, 600, 400); // Give some temporary values.
ball = new VGBall();
frame = new JFrame("VGBKernel");
vgTask = new VGTimerTask();
brick = new Brick();
}

// Create an inner TimerTask class that has access to the
// members of the VGBKernel.
class VGTimerTask extends TimerTask{
public void run(){
ball.move();
frame.repaint();
}
}

// Create an inner VGBall class that has our game logic in it.
class VGBall extends Rectangle{
int xVel, yVel; // The ball's velocity.
Color ballColor; // The color of the ball.

public VGBall(){
super(0, 0, 20, 20);
xVel = width/4;
yVel = height/4;
ballColor=new Color(0, 0, 128);
}

// Instance methods for VGBall
public void move(){
// Move the ball according to the game rules.
x+=xVel; // Move horizontally.
y+=yVel; // Move vertically.
// Detect edges and bounce if necessary.
if (x > (bounds.width - width)){
xVel = -xVel; // reverse movement.
x = bounds.width - width; // Set location to screen edge.
}
if (y > (bounds.height - height)){
yVel = -yVel; // reverse movement.
y = bounds.height - height;
}
if (x <= 0) { xVel = -xVel; x = 0; }
if (y <= 0) { yVel = -yVel; y = 0; }

// Check for intersection with Brick,
// change color when touching.
if (intersects(brick)) { ballColor=Color.GREEN; }
else { ballColor=Color.BLUE; }

}

public void draw(Graphics g){
// the ball draws itself in the graphics context given.
Color gcColor = g.getColor(); // Preserve the present color.
g.setColor(ballColor); // Use the ball's color for the ball.
g.fillRect(x, y, width, height); // Draw the ball.
g.setColor(gcColor); // Restore prior color.
} // end draw()

} // end of class VGBall

// Now the instance methods:
public void paintComponent(Graphics g){
// Get the drawing area bounds for game logic.
bounds = g.getClipBounds();
// Clear the drawing area.
g.clearRect(screen.x, screen.y, screen.width, screen.height);
// Draw the brick.
g.setColor(brick.getColor());
g.fillRect(brick.x, brick.y, brick.width, brick.height);
// Draw the ball.
ball.draw(g);
}


public static void main(String arg[]){

java.util.Timer vgTimer = new java.util.Timer(); // Create a Timer object
VGBKernel panel = new VGBKernel();

panel.frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
panel.frame.setSize(panel.screen.width, panel.screen.height);

panel.frame.setContentPane(panel);
panel.frame.setVisible(true);

// Set up the brick.
panel.brick.x = panel.screen.width/4;
panel.brick.y = panel.screen.height/4;
panel.brick.width = panel.screen.width/2;
panel.brick.height = panel.screen.height/2;

// Set up a timer to do the vgTask regularly.
vgTimer.schedule(panel.vgTask, 0, 33);
}
}

The main changes here are the addition of the brick to the VGBKernel (that's why I renamed VGKernel to VGBKernel, by the way--it's now a Video Game Brick Kernel), and I've changed ball into an extension of Rectangle to make collision detection a bit easier.

To do the actual collision detection, I'm using the method intersects() from the Rectangle class. It tests whether one Rectangle overlaps another in the coordinate space.

In this case, if the ball is on top of the brick (a rather large brick), then the ball turns green. I could have had any of a number of other effects. For example, when I was first testing this routine I didn't change the color of the ball, instead I had the ball send a message to the Java console that read "Ow!". So whenever the ball passed over the brick I'd get a long string of messages in the console like this:

Ow!
Ow!
Ow!
Ow!
Ow!
Ow!
Ow!
Ow!
Ow!

I could just as well have the program play a sound, draw an image, update a score, or any of a number of different things when a collision occurs. Exactly what should happen is determined by the rules of the video game itself.

Going Further

Try changing the effect when the ball strikes the brick. The easiest place to start would be making the ball a different color than green. To get more sophisticated, try things like adding a sound, or counting the number of moves that the ball is in contact with the brick. Change the size and location of the brick. Perhaps try adding additional bricks, and have each create a different effect. Try changing the color of the brick when the ball strikes it.

One thing about the collision detection we are doing here. It only detects when we have actually started to overlap with the brick. In many cases, it's desirable to predict when a collision will occur as a result of a move before that move occurs. Try to develop a solution to this yourself. I'll be addressing this in a future article, so you can compare what you came up with against my method. Remember that in programming there's almost never just one way to accomplish something. The same effect can usually be produced any of a number of different ways effectively enough to use in a finished program.
StumbleUpon