The Galactic War project will demonstrate just one type of game that can be created in Java. This game is complex, but that complexity is hidden inside a game engine that, once written, does not need to be opened again. You will write an applet that will inherit from the game engine, and then the vast majority of the core code for the game will be handled behind the scenes. We’ll build the game step by step, beginning with the simplistic Asteroids-style game from Chapter 3, gradually improving the game until it is finished and ready to be put up on your website. The first step to building Galactic War is to begin converting the original project from an entirely vector-based game into a bitmap-based game. We’ll start with a partial conversion in this chapter, retaining some of the vector shapes but replacing the player’s ship with a bitmap.
Here are the key topics:
Improving the game
Generalizing the vector classes
Chapter 3 gave you an example of a semi-complete Asteroids-style game to show you what would be covered in the upcoming chapters. You have now learned enough about game programming in Java to greatly enhance the game. That original game featured a class called BaseVectorShape,
which contained all of the properties needed to manipulate game objects on the screen (the asteroids, bullets, and ship). In this chapter, we’ll make a few changes to add sprite support, and in the next chapter we’ll move the game entirely over to bitmaps.
By upgrading the spaceship to an image and doing away with the vector ship (which was little more than a filled triangle), the game is really starting to look more playable. There is no substitute for bitmapped graphics. But one of the goals I set out to achieve for this game is adding the ability to use an image instead of a shape, while still retaining the existing transformation features (most importantly, real-time rotation).
You can open up the project from Chapter 3 and continue using it or you can just copy the .java files from the old project to the new project. Here are the source code files you will want to bring over to the new game from previous chapters:
BaseVectorShape.java
Asteroid.java
Bullet.java
Ship.java
SoundClip.java
MidiSequence.java
Note that I did not include the main source code file, Asteroids.java. There are a few changes needed to add image support to the game, so I will just give you the complete source code listing for the main code file, which is now called GalacticWar.java.
In the Asteroids project in Chapter 3, there were several classes to handle the ship, asteroids, and bullets in the game. Now we’re going to generalize these three classes and make them more general purpose, since a lot of code is shared among these classes. The Ship
class will be replaced entirely with an ImageEntity
(covered back in Chapter 6). Let me show you what we’re going to do with the Asteroid
and Bullet
classes.
Create a new source code file called VectorEntity.java. This class is very simple, as the following code suggests. Note that this class inherits from BaseGameEntity!
The BaseGameEntity
and ImageEntity
classes were created back in Chapter 6. Despite our having already created an awesome animation class in Chapter 7 called AnimatedSprite
, we learned that this class does not support transformations. Since we desperately need transformations for Galactic War, we will have to rely primarily on the original Sprite
class and a less capable AnimatedSprite
class that uses a scratch pad image to support both transforms and animation. This is the more advanced class that was hinted about in Chapter 7! Despite sharing the name, this is not the same class anymore.
// Vector class for handling game entities
import java.awt.*;
public class VectorEntity extends BaseGameEntity {
//variables
private Shape shape;
//accessor methods
public Shape getShape() { return shape; }
//mutator methods
public void setShape(Shape shape) { this.shape = shape; }
//default constructor
VectorEntity() {
setShape(null);
}
}
The Asteroid
class will be modified now to use VectorEntity
as a base class. This frees up a lot of code that was previously duplicated in Asteroid
and the other classes. The Asteroid
class inherits from VectorEntity,
which in turn inherits from BaseGameEntity.
You can open up the Asteroid.java file that you copied over from the project in Chapter 3, or you can just add this as a new class to the Galactic War project.
// Asteroid class derives from BaseVectorShape
import java.awt.*;
public class Asteroid extends VectorEntity {
//define the asteroid polygon shape
private int[] astx = {-20,-13, 0,20,22, 20, 12, 2,-10,-22,-16};
private int[] asty = { 20, 23,17,20,16,-20,-22,-14,-17,-20, -5};
//rotation speed
protected double rotVel;
public double getRotationVelocity() { return rotVel; }
public void setRotationVelocity(double v) { rotVel = v; }
//bounding rectangle
public Rectangle getBounds() {
Rectangle r;
r = new Rectangle((int)getX() - 20, (int) getY() - 20, 40, 40);
return r;
}
//default constructor
Asteroid() {
setShape(new Polygon(astx, asty, astx.length));
setAlive(true);
setRotationVelocity(0.0);
}
}
Much of the code in the previous Bullet
class has now been moved to VectorEntity
as well, so we can just rewrite this class and give it the specific information relevant to a bullet
object (most notably, that the getBounds()
method returns a Rectangle
that is one pixel wide and one pixel high).
// Bullet class derives from BaseVectorShape
import java.awt.*;
public class Bullet extends VectorEntity {
//bounding rectangle
public Rectangle getBounds() {
Rectangle r;
r = new Rectangle((int)getX(), (int) getY(), 1, 1);
return r;
}
Bullet() {
//create the bullet shape
setShape(new Rectangle(0, 0, 1, 1));
setAlive(false);
}
}
The actual gameplay hasn’t changed much in this new revision. Figure 11.1 shows the game with a new bitmap image being used for the player’s spaceship. However, we have upgraded the core classes significantly in this update, which will be very useful in the next chapter, where ImageEntity
will see a lot more use.
The ImageEntity,
which you learned about in Chapter 6, provides a getBounds()
method to perform collision testing. You can still toggle the bounding rectangles on and off by pressing the B key. Figure 11.2 shows the rectangle around the player’s ship. The collision code is a little too strict for a truly enjoyable game because the bounding rectangles are slightly too big. We’ll correct this by finetuning the collision code in the next two chapters.
The majority of the code remains unchanged from the Asteroids.java file back in Chapter 3, so you can just open that file and modify it as indicated. But I’m going to list the entire program again because it’s been a long time since we went over that code. I have highlighted in bold all of the lines of code that have changed. If the source code for a particular method has not changed at all, I simply commented out the code and inserted the statement //no changes needed,
so keep an eye out for this comment and then reuse that code from the Chapter 3 project. It is a beautiful testament to object-oriented programming that so few changes are needed to this source code file!
// GALACTIC WAR, Chapter 11 import java.applet.*; import java.awt.*; import java.awt.event.*; import java.awt.geom.*; import java.awt.image.*; import java.util.*; // Primary class for the game public class GalacticWar extends Applet implements Runnable, KeyListener { //the main thread becomes the game loop Thread gameloop; //use this as a double buffer BufferedImage backbuffer; //the main drawing object for the back buffer Graphics2D g2d; //toggle for drawing bounding boxes boolean showBounds = false; //create the asteroid array int ASTEROIDS = 20; Asteroid[] ast = new Asteroid[ASTEROIDS]; //create the bullet array int BULLETS = 10; Bullet[] bullet = new Bullet[BULLETS]; int currentBullet = 0; //the player's ship ImageEntity ship = new ImageEntity(this); //create the identity transform AffineTransform identity = new AffineTransform(); //create a random number generator Random rand = new Random(); //load sound effects SoundClip shoot; SoundClip explode; // applet init event public void init() { //create the back buffer for smooth graphics backbuffer = new BufferedImage(640, 480, BufferedImage.TYPE_INT_RGB); g2d = backbuffer.createGraphics(); //set up the ship ship.setX(320); ship.setY(240); ship.load("spaceship1.png"); ship.setGraphics(g2d); //set up the bullets for (int n = 0; n<BULLETS; n++) { bullet[n] = new Bullet(); } //set up the asteroids for (int n = 0; n<ASTEROIDS; n++) { ast[n] = new Asteroid(); ast[n].setRotationVelocity(rand.nextInt(3)+1); ast[n].setX((double)rand.nextInt(600)+20); ast[n].setY((double)rand.nextInt(440)+20); ast[n].setMoveAngle(rand.nextInt(360)); double ang = ast[n].getMoveAngle() - 90; ast[n].setVelX(calcAngleMoveX(ang)); ast[n].setVelY(calcAngleMoveY(ang)); } //load sound files shoot = new SoundClip("shoot.wav"); explode = new SoundClip("explode.wav"); //start the user input listener addKeyListener(this); } // applet update event to redraw the screen public void update(Graphics g) { //NO CHANGES HERE } // drawShip called by applet update event public void drawShip() { //transform and draw the ship ship.transform(); ship.draw(); //draw bounding rectangle around ship if (showBounds) { g2d.setTransform(identity); g2d.setColor(Color.BLUE); g2d.draw(ship.getBounds()); } }
There are no changes beyond this point. Please double-check the source code listing in your new Galactic War project to ensure that all of the methods following this point are included (from the project in Chapter 3). If you prefer, you may open the completed project in the resource folder sourceschapter11 GalacticWar (found at www.courseptr.com/downloads).
// drawBullets called by applet update event public void drawBullets() { //NO CHANGES HERE } // drawAsteroids called by applet update event public void drawAsteroids() { //NO CHANGES HERE } // applet window repaint event--draw the back buffer public void paint(Graphics g) { //NO CHANGES HERE } // thread start event - start the game loop running public void start() { //NO CHANGES HERE } // thread run event (game loop) public void run() { //NO CHANGES HERE } // thread stop event public void stop() { //NO CHANGES HERE } // move and animate the objects in the game private void gameUpdate() { //NO CHANGES HERE } // Update the ship position based on velocity public void updateShip() { //NO CHANGES HERE } // Update the bullets based on velocity public void updateBullets() { //NO CHANGES HERE } // Update the asteroids based on velocity public void updateAsteroids() { //NO CHANGES HERE } // Test asteroids for collisions with ship or bullets public void checkCollisions() { //NO CHANGES HERE } // key listener events public void keyReleased(KeyEvent k) { } public void keyTyped(KeyEvent k) { } public void keyPressed(KeyEvent k) { //NO CHANGES HERE } // calculate X movement value based on direction angle public double calcAngleMoveX(double angle) { //NO CHANGES HERE } // calculate Y movement value based on direction angle public double calcAngleMoveY(double angle) { //NO CHANGES HERE } }
You have now learned the most difficult and challenging aspects of writing a Java game at this point, and you are ready to start heading down the hill at a more leisurely pace in the upcoming chapters. This chapter explained:
How to add bitmaps to Galactic War
How to support multiple key presses
The following questions will help you to determine how well you have learned the subjects discussed in this chapter. The answers are provided in Appendix A, “Chapter Quiz Answers.”
The following exercise will test your comprehension of the topics covered in this chapter by making some important changes to the projects.
The Galactic War game is much more playable now than it was back in Chapter 3, thanks in part to the new keyboard handler. But we are completely ignoring a perfectly valid alternative to the keyboard—your trusty mouse. Devise a way to add mouse support to the game. You could rotate the ship when the mouse is moved left or right, and apply thrust when the mouse wheel is used, and fire when the button is pressed.
18.191.5.166