This is an exercise out of Beginning Java SE6 Game Programming, Third Edition by Jonathan S Harbour.
So I’m given an exerice in chapter 6 to take the code given to me (which puts one asteroid sprite on the screen, rotates it, and moves it with a velocity represented by a Point object) and make multiple asteroids moving around on screen. This part was easy. The second part was that I was to make the asteroids collide and bounce off of each other.
My first run at this produced many problems. The first was that the asteroids tended to stick to each other instead of bounce. I found that this was because of rapidly changing velocities due to two asteroids colliding every single frame and the velocity changing so quickly that they didn’t get out of each others way. So instead of checking for collision, I tried to predict a collision. For the purposes of the exercise, this works just fine. It also allowed me to bounce the asteroids off of each other. But it’s not completely accurate. Here’s my code:
import java.awt.*;
import java.awt.image.*;
import javax.swing.*;
import java.awt.event.*;
import java.awt.geom.*;
import java.util.*;
import java.net.*;
import java.lang.Math;
public class MultiSprite extends JFrame implements Runnable, KeyListener {
int screenWidth = 640;
int screenHeight = 480;
//double buffer objects
BufferedImage backbuffer;
Graphics2D g2d;
int MAX = 5;
Sprite[] asteroid = new Sprite[MAX];
ImageEntity background;
Thread gameloop;
Random rand = new Random();
boolean enterKeyPressed = false;
public static void main(String[] args) {
new MultiSprite();
}
public MultiSprite() {
super("Sprite Test");
setSize(640, 480);
setVisible(true);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
//create the back buffer for smooth graphics
backbuffer = new BufferedImage(screenWidth, screenHeight, BufferedImage.TYPE_INT_RGB);
g2d = backbuffer.createGraphics();
//load the background
background = new ImageEntity(this);
background.load("bluespace.png");
//load the asteroid sprites
Point point;
for (int n = 0; n<MAX; n++){
asteroid[n] = new Sprite(this, g2d);
asteroid[n].load("asteroid2.png");
point = new Point(rand.nextInt(600)+20,
rand.nextInt(440)+20);
asteroid[n].setPosition(point);
asteroid[n].setRotationRate(-5 + rand.nextInt(11));
//asteroid[n].setFaceAngle(rand.nextInt(360));
//asteroid[n].setMoveAngle(rand.nextInt(360));
asteroid[n].setAlive(true);
asteroid[n].setVelocity(new Point(-5 + rand.nextInt(11), -5 + rand.nextInt(11)));
}
enterKeyPressed = false;
addKeyListener(this);
gameloop = new Thread(this);
gameloop.start();
}
public void run() {
Thread t = Thread.currentThread();
while (t == gameloop) {
try {
Thread.sleep(30);
}
catch (InterruptedException e) {
e.printStackTrace();
}
//if game is paused
if(!enterKeyPressed){
//draw the background
g2d.drawImage(background.getImage(), 0, 0, screenWidth-1,
screenHeight-1, this);
//draw asteroids
for(int n = 0; n < MAX; n++){
if(asteroid[n].alive()) {
//asteroid[n].drawBounds(Color.YELLOW);
//warp the asteroid at screen edges
if (asteroid[n].position().getX() < -50){
asteroid[n].setPosition(new Point(getSize().width + 50, (int)asteroid[n].position().getY()));
} else if (asteroid[n].position().getX() > getSize().width + 50){
asteroid[n].setPosition(new Point(-50, (int)asteroid[n].position().getY()));
}
//warp the asteroid at screen edges
if (asteroid[n].position().getY() < -40){
asteroid[n].setPosition(new Point((int)asteroid[n].position().getX(), getSize().height + 40));
} else if (asteroid[n].position().getY() > getSize().height + 40){
asteroid[n].setPosition(new Point((int)asteroid[n].position().getX(), -40));
}
CheckCollisions(n);
asteroid[n].updatePosition();
asteroid[n].updateRotation();
asteroid[n].transform();
asteroid[n].draw();
}
}
repaint();
}
}
}
public void CheckCollisions(int n){
for(int x = 0; x < MAX; x++){
if(x!=n && PredictCollision(asteroid[n], asteroid[x])){
//we have predicted collision.
Rectangle intersectRect = asteroid[n].getBounds().intersection(asteroid[x].getBounds());
//collision on x axis: reverse x velocity
if(intersectRect.getHeight() > intersectRect.getWidth()){
asteroid[n].setVelocity(new Point(-1 * (int)asteroid[n].velocity().getX(), (int)asteroid[n].velocity().getY()));
asteroid[x].setVelocity(new Point(-1 * (int)asteroid[x].velocity().getX(), (int)asteroid[x].velocity().getY()));
}
//collision on both axis equally: reverse x and y velocity
if(intersectRect.getHeight() == intersectRect.getWidth()){
asteroid[n].setVelocity(new Point(-1 * (int)asteroid[n].velocity().getX(), -1 * (int)asteroid[n].velocity().getY()));
asteroid[x].setVelocity(new Point(-1 * (int)asteroid[x].velocity().getX(), -1 * (int)asteroid[x].velocity().getY()));
}
//collision on y axis: reverse y velocity
if(intersectRect.getHeight() < intersectRect.getWidth()){
asteroid[n].setVelocity(new Point((int)asteroid[n].velocity().getX(), -1 * (int)asteroid[n].velocity().getY()));
asteroid[x].setVelocity(new Point((int)asteroid[x].velocity().getX(), -1 * (int)asteroid[x].velocity().getY()));
}
}
}
}
public boolean PredictCollision(Sprite ast1, Sprite ast2){
if(Math.abs((ast1.position().getX() + ast1.velocity().getX()) - (ast2.position().getX() + ast2.velocity().getX())) < 60 &&
Math.abs((ast1.position().getY() + ast1.velocity().getY()) - (ast2.position().getY() + ast2.velocity().getY())) < 60){return true;}
return false;
}
public void paint(Graphics g) {
//draw the back buffer to the screen
g.drawImage(backbuffer, 0, 0, this);
}
//handle keyboard events
public void keyReleased(KeyEvent k) {}
public void keyTyped(KeyEvent k) {}
public void keyPressed(KeyEvent k) {
switch(k.getKeyCode()){
case KeyEvent.VK_ENTER:
enterKeyPressed = !enterKeyPressed;
break;
}
}
}
The two main methods to look at here are CheckCollisions and PredictCollision. Notice how I’m just reversing velocities. This seems to work ok except in instances where two asteroids are moving in the same direction, but the one in front is moving slower than the one behind it. When they collide, they both change course to move in the opposite direction whereas the one in front should get a boost and the one in back should slow down.
Does anyone have a better means of implementing this? My best guess is that vector math is probably the route to go here.
Edit: I should note that the Sprite class used here contains the velocity valuesas well as a move angle and a face angle. So I have both a speed and direction.