
From Flash To Java: The Java Version
syntax, syntax, syntax
Now that the Flash version of the game is complete, there isn't much left to do to turn this into Java. About 80% of the "transformation" into Java has to do with syntax. There is only a handful of things that are unique to Java, and the way it does things, and I will take care to go over these in more detail as they appear.
I will also point out all the basic syntax differences so you can use that as a reference later. Of course, these differences will be related to things used in the game, this is not meant to be a complete tutorial on the Java language. Just as the previous tutorial didn't cover all of Actionscript.
The Java Application
The game will be built as what is commonly called a Java Application--you might want to think of it as a desktop application. It won't be an applet, so it won't run as an embedded object in an Html page the way a Flash movie can. The reason for this is that Applets implement a different sort of framework and as I was trying to code Actionscript in the closest way possible to Java, this meant the Java application and not the applet.
But sometime in the future, I hope to teach you how to build games for mobile devices using J2ME/Android, and those applications are operated within a very strict framework, not unlike the one used in Applets only far more robust.
What is that framework? Well, when you implement an Applet interface, you get a set of controls that will dictate the life cycle of your application. This interface will/may start the application, pause it, resume it and kill it. So you must organize your code accordingly. In other words, it gives you not only a very strict and specific entry point but also strict and specific initialization and termination points.
The Java Project
By now you should have the SDK installed as well as a decent IDE. I use Eclipse so I will refer to the IDE as that from now on.
You can now create the project for the the Atlantis game. It will have the same package structure, and pretty much the same classes. In fact, you can make a copy of the Actionscript classes, change their extension to .java and move that to your project. Eclipse of course will go crazy with error checking, but let it do it. The one difference is that I changed the name of the document class from Main.as to Atlantis.java. But you don't have to.
In Flash I need the stage for certain events. And I need at least one display object so I can alter its Graphics property.
In Java I need a window, and at least one container object so I can alter its Graphics property.
But aside from that, the class structure is up to you really. I tend to use multiple Classes, lots of getters and setters, lots of inheritance, little to no implementations. I go insane with Singletons (and Java developers in general do not like Singletons)... But you don't have to do any of this, you can use one class only if that's what makes sense to you.
Just keep in mind that in a game you will often need the same basic elements: The Loop, the update logic, and the rendering.
These elements are so basic and so ubiquitous in game development that some languages have dedicated APIs and frameworks just for those steps. For instance, J2ME has a game canvas object and offers support for tile engines that would make a Flash game developer weep with jealousy.
So, relax about the classes and any confusion they might cause you and try to focus on those basic elements: the Loop, the Update, the Rendering. You will find them in every language, and then learning how to use those languages becomes a simple matter of syntax, syntax and syntax.

Java: The Document Class
all things must have a beginning
Well, they are not called Document Classes in Java. They are more like the Application class in Flex. Usually they will determine the type of application you have, if windowed, applet, midlet, bean... Since this is a windowed application, the class must extend JFrame. It means window.
public class Atlantis extends JFrame implements WindowListener {
The class then inherits from JFrame and uses the WindowListener interface, for window events.
//this method starts things rolling
public static void main(String args[]) {
new Atlantis();
}
This is the entry point of a Java application. It is always a public static method, always called main, it never returns anything, and it always receives an array of Strings as a parameter. Within it, you will usually create an instance of the application.
The method can be very useful when testing the application, as you can pass it values from a console (like command prompt in windows.)
The Constructor
public Atlantis () {
super ("Atlantis");
_gameStage = GameStage.getInstance();
this.getContentPane().add(_gameStage, "Center");
addWindowListener( this );
pack();
setResizable(false);
setVisible(true);
Sprites sprites = Sprites.getInstance();
GameController.getInstance().init();
}
Here I take care of the process of creating a window. Most of the methods here are inherited from JFrame. I pass to the JFrame constructor the title of the window: Atlantis. Then I create the GameStage and I add it to the window.
The getContentPane method is similar to the idea of contentGroup in Flex. Even the positioning logic is similar to the way it is done in Flex. No surprises there, as Flex was probably meant to be a way to attract Java developers to the Flash world.
I add the window events, passing the instance of Atlantis as the listener. I call the method pack, which basically means, put things in place before I make this thing visible.
Here I create the one instance of Sprites because in the Java version the PNG will not be embeded, but must be loaded locally, and I want to do that as soon as I can. And then I start the game calling init on GameController. Just as I did in the Flash version.
The Window Events
And now the first introduction to the event model in Java. There are basically two ways to add built in events to your objects. And the first way is: You must implement the listener interface you want to use (more on the second way later.)
The problem with this way is that you must account for every method in that interface. So all the events in WindowListener must be taken into account. Which is the reason I ended up with a lot of empty methods:
public void windowClosed(WindowEvent e) {}
public void windowOpened(WindowEvent e) {}
public void windowActivated(WindowEvent e) {}
public void windowDeactivated(WindowEvent e) {}
public void windowDeiconified(WindowEvent e) {}
public void windowIconified(WindowEvent e) {}
When all I needed is the event for window closing.
public void windowClosing(WindowEvent e) {
_gameStage.stopGame();
}
Again, I didn't have to worry about this in Flash. But think of it as a good thing. You have far more control over the Virtual Machine as you have over the Flash player. Of course, as Uncle Ben used to say, with great power comes great responsibiliy.
The First Words on Threads
So you should know that a Thread is generally a Task done parallel to your application. It doesn't necessarily mean it is done at the same time, but nowadays with the dual core revolution and multiple processors being as common as Ass, it is more probable for that to be the case.
When you create a thread, you can make it do anything, and that thing will be done almost as a separate application.
In Flash you have one thread for your application. So it works like this: do this, then do this, then do this, then do this, then do this... There is space for synchronism problems as I showed you before, but nothing major.
In Java you can build things the same way, with one thread. Incidently that one thread is called the main thread and it's started when that very same public static main method is called.
Also, I must point out that events in Java are handled in their own thread. So technically, it is pretty much impossible to use only one thread, you have the Main one and the Event one, running separately.
Running threads is a lot like the dude rotating a bunch of plates on sticks. And he has to run between the sticks and keep rotating the plates and he goes almost crazy with it and we laugh, and laugh.
If you have multiple processors you will have more than one dude helping with the plates. Otherwise you will have one fast dude, alternating between plates.
With threads the processing of the application is more like: Main Thread (do this, then do this, then do this, Thread 2: Hey sorry to butt in, but I finished the task you told me to and here is the result, cheerio!, then do this, then... Thread 3: Hello, there, I'm done too, see ya ... do this, then do ... Thread 4: done, gotta go... this.) Synchronism is everything here. Every time you run that application the Threads might come and go at different points in the life cycle of your application, and you will never control the order they come in. And this is the reason you MUST think of a thread as a separate entity. The better you separate them from resources in your main thread, the better your life as a Java developer will be.
For Game development, Threads are important because it is very common to create the main loop within a thread.
But the reason I wanted to do a first introduction to Threads here is to point out to you that the MAIN thread is created here.
Syntanx
To declare a variable, instead of:
private var myVariable:SomeObjectuse:
private SomeObject myVariable;To declare a method, instead of:
public function getMeThePresident (phoneNumber:Number):ThePresident {}use:
public ThePresident getMeThePresident (Number phoneNumber) {}The Atlantis Class
import java.awt.event.*;
import javax.swing.JFrame;
import atlantis.GameController;
import atlantis.GameStage;
import atlantis.elements.Sprites;
public class Atlantis extends JFrame implements WindowListener {
private GameStage _gameStage;
public Atlantis () {
super ("Atlantis");
_gameStage = GameStage.getInstance();
this.getContentPane().add(_gameStage, "Center");
addWindowListener( this );
pack();
setResizable(false);
setVisible(true);
Sprites sprites = Sprites.getInstance();
GameController.getInstance().init();
}
public void windowClosing(WindowEvent e) {
_gameStage.stopGame();
}
//unless using an adapter, you must declare all unused events (i don't like adapters)
public void windowClosed(WindowEvent e) {}
public void windowOpened(WindowEvent e) {}
public void windowActivated(WindowEvent e) {}
public void windowDeactivated(WindowEvent e) {}
public void windowDeiconified(WindowEvent e) {}
public void windowIconified(WindowEvent e) {}
//this method starts things rolling
public static void main(String args[]) {
new Atlantis();
}
}

Java: GameController
controlling the game states
After the document class, things get more similar to the way they were done in Flash, especially in the GameController class. I have all the same methods here, only typed differently. This is still a singleton, it inits the game by creating the first AttackWave and starting it, it also creates a new level and ends the game.
The only difference is in the way I create a Timer. Remember, GameController uses a timer to start a new level so that the player can take a breath after an AttackWave has finished or has been destroyed.
In Java, in order to create a Timer and use its TimerEvent, you once again implement a listener.
public class GameController implements ActionListener {
The ActionListener interface has only one method, ActionPerfomed, and can be used with many different types of events as it receives a generic type called ActionEvent as a parameter.
You create a timer like this:
_newLevelTimer = new Timer (GameConstants.TIME_BETWEEN_LEVELS, this);
You set the delay and the listener, meaning the object that contains the ActionPerformed method (that implements the ActionListener interface.)
The Timer can then be started, stopped, restarted... Just like the Actionscript one.
One important point however is this: I'm using the swing.Timer (check the import statements). There are older versions of Timer from other packages that work differently.
Another way to create a timer and set its listener is:
ActionListener myTask = new ActionListener() {
public void actionPerformed(ActionEvent e) {
//do your thing
}
};
_newLevelTimer = new Timer(1000, myTask);
With this, you don't need to implement the interface in GameController. And if you don't want or need to keep a reference to the timer object, that last line could be simply:
new Timer (1000, myTask).start();
This ability to create an object from an interface, in line, is pretty cool. Eat that Flash Player!
What the Virtual Machine is actually doing is creating an anonymous class which implements that interface and then creating an instance of it called myTask. Gotta love the JVM!
The actions performed by the Timer's task are:
public void actionPerformed (ActionEvent event) {
_gameStage.newLevel();
_wave.start();
_newLevelTimer.stop();
}
Syntax
Packages are written without the curly braces:
package atlantis;And the default package is not written at all (see the Atlantis.java class.)
The creation of Singletons is simplified in Java because you can have a private constructor:
private GameController (){}Null can't be checked like this:
if (myProperty)if (!myProperty)
But must be written like this:
if (myProperty != null)if (myProperty == null)
The trace function is replaced by one of many methods in System.out.* I usually use this one:
System.out.println ("hey there!");You can't create getters and setters in Java the way you do in Actionscript. But it is common form to write them like this:
public boolean getGameRunning () {
return _gameRunning;
}
public void setGameRunning (boolean value) {
_gameRunning = value;
}
The GameController class
package atlantis;
import javax.swing.Timer;
import java.awt.event.*;
import atlantis.elements.*;
public class GameController implements ActionListener {
private static GameController instance;
private GameData _gameData;
private GameStage _gameStage;
private ScreenManager _screenManager;
private AttackWave _wave;
private Timer _newLevelTimer;
private GameController (){}
public static GameController getInstance () {
if (instance == null) {
instance = new GameController();
instance.initInstance();
}
return instance;
}
private void initInstance () {
_gameData = GameData.getInstance();
_screenManager = ScreenManager.getInstance();
_newLevelTimer = new Timer (GameConstants.TIME_BETWEEN_LEVELS, this);
}
public void init () {
//grab elements
_gameStage = GameStage.getInstance();
//ship manager
_wave = new AttackWave();
_wave.start();
_gameData.setGameMode( GameConstants.PLAY );
}
public void newLevel () {
_gameData.setGameMode (GameConstants.PAUSE);
System.out.println("new level");
//is player cleared all ships in the wave, give him a bonus
if (_wave.shipsKilled == _gameData.getNumShips()) _gameData.setScore (_gameData.getScore() + GameConstants.LEVEL_COMPLETE_POINTS);
_wave = null;
_gameData.newLevel();
_screenManager.newLevel();
_wave = new AttackWave();
_gameData.setGameMode(GameConstants.PLAY);
_newLevelTimer.restart();
}
public void gameOver () {
_gameData.setGameMode (GameConstants.PAUSE);
_screenManager.clearAll();
_gameStage.showGameOver();
_gameData.setGameMode (GameConstants.GAME_OVER);
}
public void actionPerformed (ActionEvent event) {
_gameStage.newLevel();
_wave.start();
_newLevelTimer.stop();
}
}

Java: ScreenManager
update and collision
The ScreenManager has a collection of all GameSprites called _dynamicBodies. Vectors are somewhat different in Java, they require something called an Iterator to loop through them. The closest thing there is to a Flash Vector object is the ArrayList, so I'll be using that. But for now keep in mind that Vectors are an excellent option too.
This class is a singleton, and it contains the for loops that update sprites, check for collision and call their draw method.
The only differences here are the methods used in the ArrayList. You add, don't push. You remove, don't splice, and you array.get(index) and not array[index] to retrieve an item.
So yes, in this class, all changes are related to syntax only. Take a look and compare with your Actionscript class.
The ScreenManager Class
package atlantis;
import java.util.ArrayList;
import java.awt.*;
import atlantis.dynamic.*;
import atlantis.elements.*;
public class ScreenManager {
private static ScreenManager instance;
private GameData _gameData;
private GameStage _gameStage;
private ArrayList _dynamicBodies;
private ArrayList _screenSprites;
private Player _player;
//limp constructor
private ScreenManager (){}
public static ScreenManager getInstance () {
if (instance == null) {
instance = new ScreenManager();
instance.initInstance();
}
return instance;
}
private void initInstance () {
_gameData = GameData.getInstance();
_gameStage = GameStage.getInstance();
_dynamicBodies = new ArrayList();
_screenSprites = new ArrayList();
}
public ArrayList bodies () {
return _dynamicBodies;
}
public void newLevel () {
//clear any left over ship and bullets
MovingSprite body;
for (int i = _dynamicBodies.size()-1; i >= 0; i--) {
body = (MovingSprite) _dynamicBodies.get(i);
if (body instanceof Ship || body instanceof PlayerBullet) {
removeSprite(body);
_dynamicBodies.remove(i);
}
}
}
public void clearAll () {
_dynamicBodies = new ArrayList();
_screenSprites = new ArrayList();
}
public void addBody (MovingSprite body) {
if (_dynamicBodies.indexOf(body) == -1) {
_dynamicBodies.add(body);
}
}
public void removeBody (MovingSprite body) {
removeSprite(body);
int index = _dynamicBodies.indexOf(body);
if (index != -1) _dynamicBodies.remove(index);
}
public void addSprite (GameSprite sprite) {
if (_screenSprites.indexOf(sprite) == -1 ) _screenSprites.add(sprite);
}
public void removeSprite (GameSprite sprite) {
int index = _screenSprites.indexOf(sprite);
if (index != -1) _screenSprites.remove(index);
}
public void updateScreen () {
int i;
MovingSprite body;
//first, update all moving sprites
for ( i = _dynamicBodies.size()-1; i >= 0; i--) {
body = _dynamicBodies.get(i);
body.update();
}
Ship ship;
//second, check for collisions
for (i = _dynamicBodies.size()-1; i >= 0; i-- ) {
if (_gameData.getGameMode() != GameConstants.PLAY) break;
//run collision logic on bullets
body = _dynamicBodies.get(i);
if (!body.active) continue;
if (body.trashme) {
body.destroy();
continue;
}
if (body instanceof Ship) {
ship = (Ship) body;
if (ship.target != null) checkDeathRayCollision(ship);
bulletCollision(body);
}
}
//third, move all moving sprites
for ( i = _dynamicBodies.size()-1; i >= 0; i--) {
_dynamicBodies.get(i).move();
}
}
public void paintTopLayer (Graphics2D graphics) {
GameSprite sprite;
for (int i = _screenSprites.size()-1; i >= 0; i--) {
sprite = (GameSprite) _screenSprites.get(i);
if (sprite instanceof DeathRay) continue;
if (sprite.active) {
//System.out.println(sprite.type);
sprite.draw(graphics);
}
}
}
public void paintBottomLayer (Graphics2D graphics) {
for (int i = _screenSprites.size()-1; i >= 0; i--) {
if (_screenSprites.get(i) instanceof DeathRay) {;
if (_screenSprites.get(i).active) {
//System.out.println("death ray");
_screenSprites.get(i).draw(graphics);
}
}
}
}
private void bulletCollision (MovingSprite body) {
PlayerBullet bullet;
for (int i = _dynamicBodies.size()-1; i >= 0; i-- ) {
if (_dynamicBodies.get(i) instanceof PlayerBullet) {
bullet = (PlayerBullet)_dynamicBodies.get(i);
if (!bullet.active) continue;
if (body.willBeIntersecting(bullet.boundaries())) {
hitShip((Ship) body, bullet);
break;
}
if (body.isIntersecting(bullet.boundaries())) {
hitShip((Ship) body, bullet);
body.trashme = true;
break;
}
}
}
}
private void hitShip (Ship ship, PlayerBullet bullet) {
//calculate points
int points = GameConstants.ENEMY_POINTS;
ship.trashme = true;
if (ship.type == "ship_2") {
points += GameConstants.FAST_ENEMY_POINTS;
//blink stage
_gameStage.flashStage();
}
if (bullet.type != "gunCenter") {
points += GameConstants.SIDE_GUN_POINTS;
}
bullet.active = false;
bullet.trashme = true;
_gameData.setNumBullets(_gameData.getNumBullets() - 1);
_gameData.setScore(_gameData.getScore() + points) ;
}
private void checkDeathRayCollision (Ship ship) {
if (_gameData.getGameMode() != GameConstants.PLAY) return;
GameSprite target = ship.target;
//if not null
if (target != null && target.active) {
if (Math.abs((ship.nextX + (int) (ship.getWidth()/2)) - (target.x + (int) (target.getWidth()/2))) <= (int) (target.getWidth()/2)) {
if (_player == null) _player = Player.getInstance();
target.destroy();
ship.ray.destroy();
_player.updateTargets(target);
}
}
}
}

Java: GameMovie
animation states for GameSprites
Here, once again I'm using the ArrayList instead of a Vector. And all the main syntax differences are related to that.
But otherwise, the very same class.
The GameMovie class
package atlantis;
import java.awt.Rectangle;
import java.util.ArrayList;
public class GameMovie {
protected ArrayList _states;
protected int _animationCnt = 0;
protected int _animationTimer;
protected int _currentState = 0;
public GameMovie () {
_states = new ArrayList();
}
public Rectangle loopedData () {
_animationCnt++;
if (_animationCnt >= _animationTimer) {
_currentState++;
if (_currentState >= _states.size()) _currentState = 0;
_animationCnt = 0;
}
return _states.get(_currentState);
}
public Rectangle oneCycleData () {
_animationCnt++;
if (_animationCnt >= _animationTimer) {
_currentState++;
if (_currentState >= _states.size()) {
return null;
}
_animationCnt = 0;
}
return _states.get(_currentState);
}
}

Java: GameSprite
the drawable elements
Java doesn't have Sprites or MovieClips. Although in the J2ME API you do have an object called Sprite, it is meant in the traditional used in games.
This class uses an overloaded Constructor, more on this on the Syntax note below. But there are only two main differences here.
The simple one is an odd one. It has to do with collision detection. In Java, a Rectangle object will have the same methods for collision as they have in Flash, namely: intersects and intersection.
I prefer to check for collision by checking the width of the rectangle returned through intersection. Like this:
var collision:Rectangle = myRectangle.intersection(mySecondRectangle);
if (collision.width > 0) trace ("we have collision");
Mainly because in some game in the past, the other method failed me. But I can't remember when and it might have been my mistake, it probably was, but it left me with a mistrust of the intersects method that returns a Boolean.
But in Java it was the intersection method that failed me. And I would recommend you try it to see the error I saw.
In the end I had to change the rectangle collision logic to the intersects method, like this:
public boolean isIntersecting (Rectangle rect) {
//System.out.println(this.boundaries().intersects(rect));
return this.boundaries().intersects(rect);
}
The other main difference is a hugely important one. It is how a sprite is drawn:
public void draw (Graphics2D g){
Rectangle clipping = this.sourceRectangle();
if (clipping == null) return;
if (!flipped) {
g.drawImage(Sprites.IMG_SOURCE,
x,y, x + clipping.width, y+clipping.height,
clipping.x, clipping.y, clipping.x + clipping.width, clipping.y + clipping.height
, null);
} else {
//flip image horizontally
g.drawImage(Sprites.IMG_SOURCE,
//the destination rectangle
x + clipping.width, y,
x , y + clipping.height,
//the source rectangle
clipping.x, clipping.y,
clipping.x + clipping.width, clipping.y + clipping.height, null
);
}
}
The closest thing in Java to the copyPixels method from Flash is the drawImage method. And since there is no Bitmap or BitmapData, that method belongs to the Graphics property of a window, or container, or image. It would be the equivalent of writing something like this in Flash:
mySprite.graphics.drawImage(source image, destination x, destination y, destination width, destination height, source x, source y, source width, source height, null);
The null is the image observer and it is a leftover from olden times when life was rough and you had to fight for your own survival. Nowadays, it is usually set to null.
There are actually 6 overloaded drawImage methods to pick from.
When an image needs to be flipped, I don't use matrix, no such thing in Java (though you can write one), but instead, and this is something I find very cool, I simply start copying the pixels from the opposite side of the image. It is as if I have a rectangle image, but when I decide to copy it, I copy it from the right to left, but draw it from the left to right!
And like I said, in Java you can get the Graphics property from a window, a container, or another image... One way to think of it is, everything that is flat and can be painted on will have a Graphics property.
The draw method receives not the bitmapData object from the GameStage, but instead it receives as a parameter the Graphics property (or as it's commonly called: a Context) of the off screen image used in the double buffering process. But more on that later.
Syntax
In Java you can't build a method with default values for parameters like this:
public function doSomething (value:String = "default value"):void {}Instead you can use something called overloaded methods.
public void doSometing (String value) {
String myValue = value;
}
public void doSomething () {
String myValue = "default value";
}
Overloading a method is creating many versions of the same method (it might even be the constructor, creating what is known as overloaded constructors.) There are rules governing what might change between them, and you can check out the Java language specification for these. But they are pretty straightforward. They mainly dictate what can be different and what must be the same between versions.
Overloaded methods of course are not simply a way to use default values for parameters, they are incredibly useful and can sometimes reduce the need for inherited classes in an application, among other benefits.
The GameSprite Class
package atlantis.dynamic;
import java.awt.Rectangle;
import java.awt.*;
import java.awt.image.*;
import atlantis.*;
import atlantis.elements.*;
public class GameSprite {
protected ScreenManager _manager;
protected GameStage _gameStage;
protected GameData _gameData;
protected Sprites _sprites;
public boolean active = true;
public boolean trashme;
public boolean hit;
public String type;
public boolean flipped = false;
public GameMovie states;
protected int _width;
protected int _height;
public int x;
public int y;
public GameSprite () {
initSprite();
}
//overloaded constructor
public GameSprite (String type) {
this.type = type;
initSprite();
}
private void initSprite () {
_manager = ScreenManager.getInstance();
_gameData = GameData.getInstance();
_sprites = Sprites.getInstance();
_gameStage = GameStage.getInstance();
}
public Rectangle sourceRectangle (){
if (!active) return null;
return _sprites.getData(this);
}
public int getWidth () {
Rectangle image = this.sourceRectangle();
if (image != null) {
return image.width;
} else {
return 0;
}
}
public int getHeight () {
Rectangle image = this.sourceRectangle();
if (image != null) {
return image.height;
} else {
return 0;
}
}
public int right () {
return x + getWidth();
}
public int left () {
return x;
}
public int top () {
return y;
}
public int bottom () {
return y + getHeight();
}
public Rectangle boundaries () {
return new Rectangle(x, y, getWidth(), getHeight());
}
public void draw (Graphics2D g){
Rectangle clipping = this.sourceRectangle();
if (clipping == null) return;
if (!flipped) {
g.drawImage(Sprites.IMG_SOURCE,
x,y, x + clipping.width, y+clipping.height,
clipping.x, clipping.y, clipping.x + clipping.width, clipping.y + clipping.height
, null);
} else {
//flip image horizontally
g.drawImage(Sprites.IMG_SOURCE,
//the destination rectangle
x + clipping.width, y,
x , y + clipping.height,
//the source rectangle
clipping.x, clipping.y,
clipping.x + clipping.width, clipping.y + clipping.height, null
);
}
}
public void destroy () {
active = false;
}
public boolean isIntersecting (Rectangle rect) {
//System.out.println(this.boundaries().intersects(rect));
return this.boundaries().intersects(rect);
}
}
The MovingSprite Class
package atlantis.dynamic;
import java.awt.Rectangle;
import atlantis.*;
public class MovingSprite extends GameSprite {
public int vx = 0;
public int vy = 0;
public int nextX;
public int nextY;
public MovingSprite () {
super();
}
public int next_right () {
return nextX + getWidth();
}
public int next_left (){
return nextX;
}
public int next_top () {
return nextY;
}
public int next_bottom () {
return nextY + getHeight();
}
public void move () {
if (!active) return;
if (_gameData.getGameMode() == GameConstants.PAUSE) return;
x = nextX;
y = nextY;
}
public void update () {
if (!active) return;
nextX = x + vx;
nextY = y + vy;
}
public void reset () {
vx = vy = 0;
nextX = x;
nextY = y;
}
public boolean willBeIntersecting (Rectangle rect) {
Rectangle next_box = new Rectangle(nextX, nextY, getWidth(), getHeight());
return (next_box.intersects(rect));
}
}

Java: Sprites
all the little pictures
Here we have a significant difference from the Flash version, because Sprites in the Java version will load the source PNG file from the local file system. So the image must be packaged with the application. This is often done through a JAR file, but this process is not important for the sake of this tutorial.
When the singleton instance is created a call to load the image is made like this:
private void initSprites () {
_gameData = GameData.getInstance();
GraphicsEnvironment environment = GraphicsEnvironment.getLocalGraphicsEnvironment();
GraphicsConfiguration _graphicsConfig = environment.getDefaultScreenDevice().getDefaultConfiguration();
try {
BufferedImage image = ImageIO.read(new File(GameConstants.IMAGE_DIR +"\\_sprites_png.png"));
int transparency = image.getColorModel().getTransparency();
IMG_SOURCE = _graphicsConfig.createCompatibleImage(
image.getWidth(), image.getHeight(),
transparency );
Graphics2D g2d = IMG_SOURCE.createGraphics();
g2d.drawImage(image,0,0,null);
g2d.dispose();
}
catch(IOException e) {
System.out.println("Could Not Load Image Source" );
}
}
Here we have the first glimpse at creating an image object. And I will walk through that process in a second. First I want to explain a few things regarding the types of image objects you can have.
The more common types extend the Image class, which is an abstract class and it cannot be instantiated, but you may type cast an object as Image just as you would with a super class.
The BufferedImage is one of the classes that extends the abstract Image and VolatileImage is the other type, but it is not common to use it with games.
You might use a class called ImageIcon to load an image from the file system or from a URL, and then use the ImageIcon.getImage() method to retrieve an Image object. But ImageIcon is not itself an image.
What does Buffered Means?
Java is filled with Buffers. And this fact, after Threads, is the main reason I like Java over Actionscript. You can have buffered versions of strings, images, net connections... You can write to a file through a buffer and read from a file through a buffer, and you can chain many buffers together and make IO processing as smooth as snakeskin.
The keyword Buffer should make you think of better memory management and allocation. Whether something will be stored in virtual memory, or use the memory from the video card, or... whatever. It allows you to create a temporary version of the object or data you are handling and that temporary version can be gotten rid of at any moment you desire, or cast into something else. It also allows you to create objects with only partially loaded data.
There are many other advantages to buffers, particularly when reading and writing data, but for the image part of it, just remember BufferedImages are very nice objects indeed!
Creating a BufferedImage
In this class I create the Source Image object from the loaded PNG. But you can create an empty BufferedImage as well. The closest thing to it in Flash would be the BitmapData object. But not quite the same. You can create a new BufferedImage like this:
BufferedImage image = new BufferedImage(300, 300, BufferedImage.TYPE_INT_ARGB );
You pass it the width, height, and image type.
Another way is to create a BufferedImage is to make a copy from another Image type object, which is how the Sprites class creates the IMG_SOURCE object.
To do that, first I read the loaded image into a BufferedImage.
BufferedImage image = ImageIO.read(new File(GameConstants.IMAGE_DIR +"\\_sprites_png.png"));
Then I grab the transparency value of the image. An optional step.
int transparency = image.getColorModel().getTransparency();
You noticed I create two variables at the top of the initSprites method. One is the local graphics environment: the data related to the video card in the machine currently running the application. From the graphics enviroment I get the second property, which is the data concerning the configuration of the video card and screen.
GraphicsEnvironment environment = GraphicsEnvironment.getLocalGraphicsEnvironment(); GraphicsConfiguration _graphicsConfig = environment.getDefaultScreenDevice().getDefaultConfiguration();
With that, I can tell the object holding the configuration data to create a BufferedImage object which is compatible to the user's video configurations. This buffered image is still empty, it has however the size of the loaded image and the same transparency.
IMG_SOURCE = _graphicsConfig.createCompatibleImage( image.getWidth(), image.getHeight(), transparency );
Next, I create the graphics context for that empty image. Creating a canvas.
Graphics2D g2d = IMG_SOURCE.createGraphics();
And then I draw a copy of the loaded image into the new BufferedImage's graphics context.
g2d.drawImage(image,0,0,null); g2d.dispose();
This overloaded drawImage method requires an image source, the x and y values of where the image will be drawn (always top left) and null for the image observer/listener.
Note: The dispose method in Flash has a very different function. Here it means, clear the data used to draw on this graphic context now that I have it drawn. It clears that out from memory but it does not clear the width and height of the new object data. I don't know what Adobe meant dispose to be used for in Flash... It never made any sense to me.
Why Graphics2D?
Version 1.4 of Java introduced these new classes. They can't be used in the old versions of Java. The 2D usually marks the new version, so you have Graphics and Graphics2D which extends the first one. But you also have Rectangle and Rectangle2D, Point and Point2D...
Some operations will require that you change the graphics context from the first version to the second one. So you do a cast:
Graphics2D newG = (Graphics2D) oldG;The rest of the Sprites class is the same as the Flash one. With one little tiny bit of difference. You can't have SWITCHS based on anything but integers in Java. So the correct way to code that in Flash, if I wanted to keep the switch here, would be to create constants for each image type, with integer values and not the string objects. So something like:
public static const SEA_SURFACE_IMG:int = 0; public static const SHIP1_IMG:int = 1;
That way I could switch on an integer, but still make the code readable.
But alas, I decided to use a long series of else if statements.
The Sprites Class and its Internal Classes
package atlantis.elements;
import java.awt.*;
import java.awt.image.*;
import java.io.*;
import java.util.ArrayList;
import javax.imageio.ImageIO;
import atlantis.GameData;
import atlantis.GameConstants;
import atlantis.GameMovie;
import atlantis.dynamic.GameSprite;
public class Sprites {
private static Sprites instance;
private GameData _gameData;
public static BufferedImage IMG_SOURCE;
private Sprites (){}
public static Sprites getInstance () {
if (instance == null) {
instance = new Sprites();
instance.initSprites();
}
return instance;
}
public Rectangle getData (GameSprite element) {
String key = element.type;
Rectangle result;
if (key == "seaSurface") {
if (element.states == null) element.states = new SeaSurface();
return element.states.loopedData();
} else if (key == "ground") {
if (_gameData.getGameMode() != GameConstants.GAME_OVER) {
return new Rectangle(0,348,640,134);
} else {
return new Rectangle(0,215,640,134);
}
} else if (key == "base_3") {
if (element.states == null) element.states = new Base3();
return element.states.loopedData();
} else if (key == "base_4") {
if (element.states == null) element.states = new Base4();
return element.states.loopedData();
} else if (key == "base_6") {
if (element.states == null) element.states = new Base6();
return element.states.loopedData();
} else if (key == "ship_3") {
if (element.states == null) element.states = new Ship3();
return element.states.loopedData();
} else if (key == "deathRay") {
if (element.states == null) element.states = new Ray();
return element.states.loopedData();
} else if (key == "boomSmall") {
if (element.states == null) element.states = new BoomSmall();
result = element.states.oneCycleData();
if (result == null) element.states = null;
return result;
} else if (key == "boomLarge") {
if (element.states == null) element.states = new BoomLarge();
result = element.states.oneCycleData();
if (result == null) element.states = null;
return result;
} else if (key == "boomXLarge") {
if (element.states == null) element.states = new BoomXLarge();
result = element.states.oneCycleData();
if (result == null) element.states = null;
return result;
} else if (key == "ufo") {
if (element.states == null) element.states = new TheEnd();
return element.states.loopedData();
} else if (key == "gun_right") {
return new Rectangle(399,12,32,16);
} else if (key == "sea") {
return new Rectangle(0,114,640,100);
} else if (key == "base_1") {
return new Rectangle(64,32,62,14);
} else if (key == "base_2") {
return new Rectangle(559,12,64,16);
} else if (key == "base_5") {
return new Rectangle(0,32,64,40);
} else if (key == "gun_side") {
return new Rectangle(399,12,32,16);
} else if (key == "gun_middle") {
return new Rectangle(431,12,32,16);
} else if (key == "ship_1") {
return new Rectangle(339,12,60,16);
} else if (key == "ship_2") {
return new Rectangle(120,12,36,14);
} else if (key == "bullet") {
return new Rectangle(156,12,4,4);
}
return null;
}
private void initSprites () {
_gameData = GameData.getInstance();
GraphicsEnvironment environment = GraphicsEnvironment.getLocalGraphicsEnvironment();
GraphicsConfiguration _graphicsConfig = environment.getDefaultScreenDevice().getDefaultConfiguration();
try {
BufferedImage image = ImageIO.read(new File(GameConstants.IMAGE_DIR +"\\_sprites_png.png"));
int transparency = image.getColorModel().getTransparency();
IMG_SOURCE = _graphicsConfig.createCompatibleImage(
image.getWidth(), image.getHeight(),
transparency );
Graphics2D g2d = IMG_SOURCE.createGraphics();
g2d.drawImage(image,0,0,null);
g2d.dispose();
}
catch(IOException e) {
System.out.println("Could Not Load Image Source" );
}
}
private class Base3 extends GameMovie {
public Base3() {
super();
_states.add(new Rectangle(527,12,16,16));
_states.add(new Rectangle(543,12,16,16));
_animationTimer = 5;
}
}
private class Base4 extends GameMovie {
public Base4() {
super();
_states.add(new Rectangle(495,12,16,16));
_states.add(new Rectangle(511,12,16,16));
_animationTimer = 4;
}
}
private class Base6 extends GameMovie {
public Base6() {
super();
_states.add(new Rectangle(463,12,16,16));
_states.add(new Rectangle(479,12,16,16));
_animationTimer = 4;
}
}
private class Ship3 extends GameMovie {
public Ship3() {
super();
_states.add(new Rectangle(0,12,60,14));
_states.add(new Rectangle(60,12,60,14));
_animationTimer = 5;
}
}
private class SeaSurface extends GameMovie {
public SeaSurface() {
super();
_states.add(new Rectangle(64,0,560,6));
_states.add(new Rectangle(64,6,560,6));
_animationTimer = 5;
}
}
private class Ray extends GameMovie {
public Ray() {
super();
_states.add(new Rectangle(160,12,164,10));
_states.add(new Rectangle(160,22,164,10));
_animationTimer = 5;
}
}
private class BoomSmall extends GameMovie {
public BoomSmall() {
super();
_states.add(new Rectangle(348,32,28,14));
_states.add(new Rectangle(376,32,28,14));
_states.add(new Rectangle(404,32,28,14));
_states.add(new Rectangle(432,32,28,14));
_states.add(new Rectangle(460,32,28,14));
_animationTimer = 5;
}
}
private class BoomLarge extends GameMovie {
public BoomLarge() {
super();
_states.add(new Rectangle(128,32,44,12));
_states.add(new Rectangle(172,32,44,12));
_states.add(new Rectangle(216,32,44,12));
_states.add(new Rectangle(260,32,44,12));
_states.add(new Rectangle(304,32,44,12));
_animationTimer = 4;
}
}
private class BoomXLarge extends GameMovie {
public BoomXLarge() {
super();
_states.add(new Rectangle(0,70,75,44));
_states.add(new Rectangle(75,70,75,44));
_states.add(new Rectangle(150,70,75,44));
_states.add(new Rectangle(225,70,75,44));
_states.add(new Rectangle(300,70,75,44));
_animationTimer = 4;
}
}
private class TheEnd extends GameMovie {
public TheEnd() {
super();
_states.add(new Rectangle(0,0,32,10));
_states.add(new Rectangle(32,0,32,10));
_animationTimer = 10;
}
}
}

Java: The City
collective drawing
I used the City class in the Flash game to show you how to draw more than one element on a separate bitmapData object and then add that object to the GameStage bitmapData object. And here in the Java version I do the same thing but using the Graphics object.
For this, I use a BufferedImage object called image. And I once again use the graphics configuration data to create a compatible image. So the first three lines of the draw method creates the new image and its graphics context .
int transparency = Sprites.IMG_SOURCE.getColorModel().getTransparency(); BufferedImage image = _graphicsConfig.createCompatibleImage(GameConstants.SCREEN_WIDTH, 134, transparency); Graphics2D g2D = image.createGraphics();
//draw the sea g2D.drawImage(Sprites.IMG_SOURCE,1,24,1+_sea.width, 24+_sea.height, _sea.x, _sea.y, _sea.x + _sea.width, _sea.y + _sea.height , null);
Then if gameMode equals PLAY I draw the sea surface, which you will recall is an animation.
//draw sea surface animation
if (_gameData.getGameMode() == GameConstants.PLAY) {
//add the sea surface
Rectangle surfaceRect = _surface.sourceRectangle();
g2D.drawImage(Sprites.IMG_SOURCE,
32, 24, 32 + surfaceRect.width, 24 + surfaceRect.height,
surfaceRect.x, surfaceRect.y, surfaceRect.x + surfaceRect.width, surfaceRect.y + surfaceRect.height,
null);
}
These two elements are drawn onto the new image. The data is disposed of and the new image is copied to the main buffered image in GameStage.
g2D.dispose(); g.drawImage(image, 0, 240, null);
Then comes the call to ScreenManager to add the death ray, if any is present.
_manager.paintBottomLayer(g);
Then I add the city ground graphic, which can have two different colors based on GameMode and so a call to Sprites is made for the correct sourceRectangle.
//draw the ground Rectangle groundRect = _ground.sourceRectangle(); g.drawImage(Sprites.IMG_SOURCE,0,240,640,374, groundRect.x, groundRect.y, groundRect.x + groundRect.width, groundRect.y + groundRect.height, null);
But these last elements you noticed are added straight onto the GameStage graphics context. And this is how you can combine elements in one image and then draw that image onto another.
The City Class
package atlantis.elements;
import java.awt.*;
import java.awt.image.*;
import atlantis.*;
import atlantis.dynamic.GameSprite;
public class City extends GameSprite {
private Rectangle _sea;
private GameSprite _ground;
private GameSprite _surface;
private GraphicsConfiguration _graphicsConfig;
public City () {
_sea = new GameSprite("sea").sourceRectangle();
_surface = new GameSprite("seaSurface");
_ground = new GameSprite ("ground");
GraphicsEnvironment environment = GraphicsEnvironment.getLocalGraphicsEnvironment();
_graphicsConfig = environment.getDefaultScreenDevice().getDefaultConfiguration();
}
public void draw (Graphics2D g) {
int transparency = Sprites.IMG_SOURCE.getColorModel().getTransparency();
BufferedImage image = _graphicsConfig.createCompatibleImage(GameConstants.SCREEN_WIDTH, 134, transparency);
Graphics2D g2D = image.createGraphics();
//draw the sea
g2D.drawImage(Sprites.IMG_SOURCE,1,24,1+_sea.width, 24+_sea.height,
_sea.x, _sea.y, _sea.x + _sea.width, _sea.y + _sea.height
, null);
//draw sea surface animation
if (_gameData.getGameMode() == GameConstants.PLAY) {
//add the sea surface
Rectangle surfaceRect = _surface.sourceRectangle();
g2D.drawImage(Sprites.IMG_SOURCE,
32, 24, 32 + surfaceRect.width, 24 + surfaceRect.height,
surfaceRect.x, surfaceRect.y, surfaceRect.x + surfaceRect.width, surfaceRect.y + surfaceRect.height,
null);
}
g2D.dispose();
g.drawImage(image, 0, 240, null);
_manager.paintBottomLayer(g);
//draw the ground
Rectangle groundRect = _ground.sourceRectangle();
g.drawImage(Sprites.IMG_SOURCE,0,240,640,374,
groundRect.x, groundRect.y, groundRect.x + groundRect.width,
groundRect.y + groundRect.height, null);
}
}

Java: Player, Gun, PlayerBullet, Base
another type of grouping
The Player class has a different sort of element grouping. It doesn't draw more than one element, it doesn't even extend the GameSprite class, but it does create the guns and the bases (the tiny buildings) and keeps track of which element has been destroyed.
But besides the use of ArrayLists instead of Vectors, the class is exactly the same as before. The same goes for Gun, PlayerBullet and Base.
The Player Class
package atlantis.elements;
import java.util.ArrayList;
import atlantis.*;
import atlantis.dynamic.GameSprite;
public class Player {
private static Player instance;
private Gun _leftGun;
private Gun _rightGun;
private Gun _centerGun;
private Base _base1;
private Base _base2;
private Base _base3;
private Base _base4;
private Base _base5;
private Base _base6;
private GameData _gameData;
private ArrayList _bases;
private ArrayList _guns;
private Player (){}
public static Player getInstance () {
if (instance == null) {
instance = new Player();
instance.initInstance();
}
return instance;
}
private void initInstance () {
_gameData = GameData.getInstance();
_bases = new ArrayList ();
_guns = new ArrayList();
_leftGun = new Gun(0,248, "gun_side");
_rightGun = new Gun(608,224, "gun_right");
_centerGun = new Gun(288,224, "gun_middle");
_base1 = new Base(64, 342, "base_1");
_base2 = new Base(150, 296, "base_2");
_base3 = new Base(248, 274, "base_3");
_base4 = new Base(328, 248, "base_4");
_base5 = new Base(384, 318, "base_5");
_base6 = new Base(568, 272, "base_6");
_guns.add(_leftGun);
_guns.add(_rightGun);
_guns.add(_centerGun);
_bases.add(_base1);
_bases.add(_base2);
_bases.add(_base3);
_bases.add(_base4);
_bases.add(_base5);
_bases.add(_base6);
}
public void shoot (boolean left, boolean right) {
if (_gameData.getGameMode() != GameConstants.PLAY) return;
if (_gameData.getNumBullets() == GameConstants.NUM_BULLETS_ON_SCREEN) return;
if ((left && right) || (!left && !right) ) {
//shoot with the center gun
if (_guns.indexOf(_centerGun) != -1 ) _centerGun.shoot();
} else {
//shoot with left gun
if (left) {
if (_guns.indexOf(_leftGun) != -1) _leftGun.shoot();
//shoot with right gun
} else {
if (_guns.indexOf(_rightGun) != -1) _rightGun.shoot();
}
}
}
public GameSprite getTarget () {
if (_gameData.getGameMode() != GameConstants.PLAY) return null;
int index;
GameSprite target;
if (Math.random() > 0.5 && _guns.size() > 1) {
//pick gun
index = (int) Math.floor(Math.random()*_guns.size());
target = (GameSprite) _guns.get(index);
} else {
//pick base
index = (int) Math.floor(Math.random()*_bases.size());
target = (GameSprite) _bases.get(index);
}
return target;
}
public void updateTargets (GameSprite target) {
if (target instanceof Gun) {
_guns.remove(_guns.indexOf(target));
} else {
_bases.remove(_bases.indexOf(target));
}
if (_bases.size() == 0) {
System.out.println ("GAME OVER");
GameController.getInstance().gameOver();
}
}
public void upgrade () {
if (_guns.size() < 3) {
//recover one gun
if (_guns.indexOf(_leftGun) == -1) {
_leftGun.refresh();
_guns.add(_leftGun);
} else if (_guns.indexOf(_rightGun) == -1) {
_rightGun.refresh();
_guns.add(_rightGun);
} else {
_centerGun.refresh();
_guns.add(_centerGun);
}
}
}
}
The Gun Class
package atlantis.elements;
import javax.swing.Timer;
import java.awt.event.*;
import java.awt.Point;
import atlantis.dynamic.GameSprite;
import atlantis.*;
public class Gun extends GameSprite implements ActionListener {
private Timer _timer;
public Gun (int _x, int _y, String _type) {
super();
x = _x;
y = _y;
type = _type;
if (type == "gun_right") {
flipped = true;
}
_manager.addSprite(this);
_timer = new Timer(GameConstants.TIME_BETWEEN_SHOTS, this);
}
public void actionPerformed (ActionEvent event) {
_timer.stop();
}
//position of the tip of the gun
public Point origin () {
if (type == "gun_middle") {
return new Point(x + (int)(getWidth()/2) - 4, y);
} else if (type == "gun_side") {
return new Point(x + getWidth() - 6, y);
} else {
return new Point(x + 6, y + 4);
}
}
public void shoot () {
if (_timer.isRunning()) return;
_timer.restart();
_gameData.setNumBullets(_gameData.getNumBullets() + 1);
PlayerBullet bullet = new PlayerBullet(type, this.origin());
_manager.addBody(bullet);
_manager.addSprite(bullet);
}
public void destroy () {
super.destroy();
_manager.removeSprite(this);
//add explosion
_manager.addSprite(new Boom(x, y, "boomSmall"));
}
public void refresh () {
active = true;
_manager.addSprite(this);
}
}
The PlayerBullet Class
package atlantis.elements;
import java.awt.Point;
import atlantis.dynamic.MovingSprite;
import atlantis.*;
public class PlayerBullet extends MovingSprite {
public PlayerBullet (String gun, Point origin) {
type = "bullet";
x = origin.x;
y = origin.y;
if (gun == "gun_middle") {
vx = 0;
vy = -GameConstants.BULLET_SPEED;;
} else if (gun == "gun_side") {
vx = GameConstants.BULLET_SPEED*2;
vy = -(GameConstants.BULLET_SPEED - 5);
} else {
vx = -GameConstants.BULLET_SPEED*2;
vy = -(GameConstants.BULLET_SPEED - 6);
}
}
public void move () {
if (_gameData.getGameMode() != GameConstants.PLAY) return;
super.move();
if (x > GameConstants.SCREEN_WIDTH
|| x + getWidth() < 0
|| y + getHeight() < 0
|| y > GameConstants.SCREEN_HEIGHT) {
destroy();
}
}
public void destroy () {
if (active) {
active = false;
_manager.removeBody(this);
}
_gameData.setNumBullets(_gameData.getNumBullets() - 1);
}
}
The Base Class
package atlantis.elements;
import atlantis.dynamic.GameSprite;
public class Base extends GameSprite {
public Base (int _x, int _y, String _type) {
x = _x;
y = _y;
type = _type;
_manager.addSprite(this);
}
public void destroy () {
super.destroy();
_manager.removeSprite(this);
//add explosion
if (type == "base_1" || type == "base_2" || type == "base_5") {
_manager.addSprite(new Boom(x + 7, y, "boomLarge"));
} else {
_manager.addSprite(new Boom(x, y, "boomSmall"));
}
}
}

Java: the GameStage
the main canvas and loop
If GameStage was the heart of the game in the Flash version, in the Java version it is not just the heart of the application but it's where we'll see most of the differences. It is here you will write your first thread, and paint the window with the game's graphics.
The differences start from the very beginning, on the class declaration.
GameStage extends the container JPanel, and implements Runnable and ActionListener. And by the way, here is where you will see "the other method" for adding events to a class: The Adapter. But back to the class declaration:
public class GameStage extends JPanel implements Runnable, ActionListener
Runnable means Thread. You can create a new Thread the same way you create a Timer. Just say something is a new Thread and pass it a Runnable class, which must have at least one implemented method called run. Like this:
Thread myThread = new Thread(new Runnable () {
public void run () {
//the thread;
}
}
);
The thread, just like a timer, must be started with:
myThread.start();
So one class can create many Threads this way.
The other way to use threads is to implement Runnable in your class. That interface has one method: run. So your class must have that method.
At one point in your code you will create the Thread, and pass it the instance of the class that implements Runnable. So for instance. GameStage implements Runnable. I created a property called _loop which is a Thread. And then in the createStage method:
private void createStage () {
_city = new City();
_player = Player.getInstance();
_gameInfo = GameInfo.getInstance();
_loop = new Thread(this);
_loop.start();
}
I create the new Thread, pass it the instance of GameStage, and tell the Thread to start.
On the Nature of Threads
I said before that you could think of a Thread AS a loop. While that is somewhat correct, you should never think a thread IS a loop.
A thread is a task. The task lives inside the run method. Whatever is inside that method will be run as a separate program, or nearly so. It is what exists in that run method that is said to be running on a separate thread.
But the run method is not a loop. It can be called and be done with as fast as any trace() call in Actionscript. In fact this is the life cycle of a thread: It begins on the first line of the run method, and once it reaches the end of that method, the thread is gone. Or exited.
So in order to have a game loop inside a thread, I must use a while loop with a condition related to whether the game is running or not. So let me go over the main loop now:
public void run () {
long startTime, waitTime, elapsedTime;
running = true;
while(running) {
startTime = System.nanoTime();
if (_gameData.getGameMode() != GameConstants.PAUSE) {
_screenManager.updateScreen();
renderStage();
paintStage();
}
elapsedTime = System.nanoTime() - startTime;
waitTime = Math.max(_delayTime - elapsedTime, 5/10000L);
try {
TimeUnit.NANOSECONDS.sleep(waitTime);
} catch(InterruptedException e) {}
}
System.exit(0);
}
The JVM will call run and it is up to the JVM to decide when to do it. But don't worry, this call will happen very fast.
I start by declaring some variables of type long. These are somewhat equivalent to Number in Actionscript.
I set the running property to true, meaning the game is currently running. These two lines of code will run only once, as the run method of the thread will be called only once.
Next comes the while loop. While running equals true I will never escape this loop. And so this is the perfect spot to place all my game loop logic.
If the game is not Paused, I update the elements, call renderStage which updates the "off screen" buffered image, and then paint the new screen.
Once all that is done I calculate the time it took to do all this. Java has a lot more features to calculate elapsed time, and I wish Flash had them too. For instance you can use nano seconds to calculate the time between iterations.
The important bit is here:
waitTime = Math.max(_delayTime - elapsedTime, 5/10000L);
This line of code selects the highest number. Either the elapsed time, or 5 miliseconds.
You don't want your thread to work non-stop. You want, at some point, to make it sleep for a while. Like 5 milliseconds in this example. When this happens, a Thread is said to YIELD to any other threads that might be waiting to do their thing. As well as allowing the Garbage collector to do its thing.
But why are there threads waiting if threads are supposed to be independent? This is a somewhat advanced topic. It has to do with synchronism, with shared resources, with locks and keys... Any book or article on threads will explain all this. I'd recommend the chapters on thread from the excellent (and free) book Thinking in Java. But for the purpose of this tutorial, just remember that pretty much in every game done in Java, the thread where the main loop resides will be made to sleep in every iteration if possible. If not because of other threads, then because of the garbage collector. Remember all those dispose() calls on graphics, well, this is when all that data will go poof!!! into thin air; freeing some of our memory. And remember that events run on a separate thread, so if you want to listen to key presses for instance you will have to yield the main thread to the event thread at some point, otherwise you get no key presses.
By the time I reach this line:
System.exit(0);
It means that not only the game is over, but the application is finito!
The Listeners
The other thing you noticed in the class declaration is that I implement our old friend ActionListener. This is used for the blink timer. Here is the event:
//action for our timer listener
public void actionPerformed(ActionEvent event) {
//if event comes from the timer
if (event.getSource() == _blinkTimer) {
blink();
}
}
Notice how you can check to see what event this method is handling. In case you have multiple timers, for instance. It is quite useful, I think, and the timers can still have their own individual delays.
The blink logic is the same here as it was in the Flash version.
The JPanel
This is a form of container. If you have used Flex you know what this means. I could have used other types: for instance, it is very common to use a container called Canvas for game development. In J2ME you will use a special container called GameCanvas. Just remember JFrame is a window and JPanel is a container put inside that window.
The main configuration of the JPanel, just like in JFrame, is done in the Constructor.
private GameStage (){
super(true);
setBackground(Color.black);
setPreferredSize( new Dimension(GameConstants.SCREEN_WIDTH, GameConstants.SCREEN_HEIGHT));
setExitShortCuts();
setFocusable(true);
requestFocus();
_blinkTimer = new Timer(GameConstants.TIME_BETWEEN_FLASHES, this);
_delayTime = 1000/GameConstants.FPS;
_delayTime = TimeUnit.NANOSECONDS.convert(_delayTime, TimeUnit.MILLISECONDS);
}
The super(true) call is setting a property call isDoubleBuffered to true. It initializes something called a buffering strategy. This will help with memory management as we draw onto the "off-screen" image in GameStage and when that gets transferred to the screen itself. But the heavy lifting is done by the JVM, so just set the property to true and relax.
Next I set the background color and the size. I call my own method, setExitShortCuts which will add key events to close the window. I bring the focus to this window and the rest is pretty much the same. Note: setBackground, setPreferredSize, setFocusable and requestFocus are all methods inherited from JPanel or its parent classes.)
I have a delayTime property used in the loop and that is the calculation of how long a "frame" will take. Divide 1000 milliseconds by the Frame per Seconds rate you want, then convert that to Nanoseconds since that is the unit of time we're using in the loop. You could use milliseconds there as well, but Java wanted to show-off, and so I let it.
The Jpanel has one method in particular that is very important, and must be ignored. That is the paint method. This is the method that paints the screen. So why must that be ignored? Because you don't know when it is called, you can't tell the Panel to paint itself, and hope the JVM will immediately change the screen. There is even a paintImmediately method in Java, but don't bother with it either. It lies!!!
What you must do instead is something called active rendering. Where you take control of what is painted and when. That process is done through two methods, both called from within the main loop:
private void renderStage () {
if (_bufferImage == null){
_bufferImage = createImage(GameConstants.SCREEN_WIDTH, GameConstants.SCREEN_HEIGHT);
return;
} else {
_bufferGraphics = (Graphics2D) _bufferImage.getGraphics();
}
// clear the background
_bufferGraphics.setColor(Color.decode(_gameData.getBackgroundColor()));
_bufferGraphics.fillRect (0, 0, GameConstants.SCREEN_WIDTH, GameConstants.SCREEN_HEIGHT);
//paint all individual elements
if (_city != null) {
_city.draw(_bufferGraphics);
}
_screenManager.paintTopLayer(_bufferGraphics);
//draw gameInfo
_gameInfo.draw(_bufferGraphics);
}
Render stage will create the buffered image with all game elements currently visible. It starts by filling the image with a rectangle set in the current background color. Remember this color changes in the blink timer, causing the background to "blink."
Then I draw the city, and the topLayer elements (ships, guns, buildings, explosions, bullets...) and the scores. But this BufferedImage is an "off screen" image. It is sort of the equivalent of having a Sprite in Flash but never use addChild to add it to the stage. So it exists in a kind of memory limbo. It's how the JVM manages that limbo that can make all the difference.
Then in the next method:
private void paintStage () {
if (_bufferImage == null) return;
Graphics g;
try {
g = this.getGraphics();
if ((g != null) && (_bufferImage != null))
g.drawImage(_bufferImage, 0, 0, null);
g.dispose();
} catch (Exception e) {
System.out.println("error with graphics: " + e);
}
}
I draw that buffered image onto the graphics context of the JPanel.
The drawing done to this "off screen" buffered image and the subsequent painting of this image onto the screen create the process known as double buffering.
KeyEvents
When I first create an instance of GameStage, I register the key events that will control the shooting. And this time I use something called KeyAdapters:
private void initInstance () {
_gameData = GameData.getInstance();
_sprites = Sprites.getInstance();
_screenManager = ScreenManager.getInstance();
addKeyListener( new KeyAdapter() {
public void keyPressed(KeyEvent event) {
on_KeyDown(event);
}
public void keyReleased(KeyEvent event) {
on_KeyUp(event);
}
});
createStage();
}
Adapters are great because with them you don't have to worry about implementing methods you don't need. The KeyAdapter implements the KeyListener and has empty methods for all events already in place. All you need to do is override the ones you need.
Otherwise you would have to make GameStage implement KeyListener and then the code would be:
addKeyListener(this);
And then you would have to implement ALL methods in the KeyListener interface.
Another place where I use key events is when I set the exit shortcuts.
private void setExitShortCuts () {
addKeyListener( new KeyAdapter() {
public void keyPressed(KeyEvent e)
{ int keyCode = e.getKeyCode();
if ((keyCode == KeyEvent.VK_ESCAPE) || (keyCode == KeyEvent.VK_Q) ||
(keyCode == KeyEvent.VK_END) ||
((keyCode == KeyEvent.VK_C) && e.isControlDown()) ) {
running = false;
}
}
});
}
Notice that I create a new KeyAdapter but the events don't conflict with each other because I am listening to different keys. So if the player has pressed Control+Escape, or Control+Q, or Control+END, or Control+C, then I change the value of running to false, which you will remember ends the while loop, which ends the game thread.
Synchronism And The Running Property
The running boolean seems like a place I might run into problems, doesn't it? I have other methods setting running to false, while I have a thread checking on it. In fact, I can have key events determining the value of running. And remember that events run on a separate thread, and yet I have another thread reading the same property. And I could even have other objects changing the value of running.
To be honest, this game doesn't offers much in terms of problems with synchronism. But if it did, here it is how Java can help.
One thing I could do is declare the running property like this:
public volatile boolean running;
Ooooh, volatile! What does it mean? Well, it turns out that when you create a public variable that can be changed by more than one object, those objects might, to speed things up, store their own temporary version of that property inside themselves, like a copy. So that they don't have to check with the source of that property every time. This is done by the JVM, and in most cases it doesn't create a problem. Unless synchronism is important. Then what the keyword volatile means is "JVM, my old friend, do not allow other objects to store temporary versions of this property, but use one, and only one version of it, in fact, this one!"
But the best way to handle synchronism problems is: the synchronized keyword. It can be used in a variable, method, entire class or a block of code like this:
synchronized (this) {
//the code here is synchronized
}
And what does synchronized do? It makes sure only one object can have access to the synchronized thing at a time. So if it is a property, like running, I could not change its value if something else had gotten there first and was in the process of reading that property or changing it. Once they were done with whatever they were doing, then other objects can access that property, but again, only one at a time.
When you synchronize a class, all its methods and properties can only be read or written to by one outside object at a time. It does of course have a cost in memory and processing, and must be used with care.
I said the synchronized keyword was the best way to deal with problems with synchronism. But in reality, good code design is the best solution. Synchronization is perfect when you have no idea when certain things might change and in what order, and you must make sure they don't overlap.
But good design will reduce the number of properties that fall into that category.
The GameStage Class
package atlantis;
import javax.swing.*;
import java.awt.image.*;
import java.awt.event.*;
import java.awt.*;
import java.util.concurrent.*;
import atlantis.elements.*;
public class GameStage extends JPanel implements Runnable, ActionListener {
private static GameStage instance;
public boolean running;
private ScreenManager _screenManager;
private GameData _gameData;
private Sprites _sprites;
private City _city;
private Player _player;
private GameInfo _gameInfo;
private Ufo _ufo;
//key input variables
private boolean _pressedRight = false;
private boolean _pressedLeft = false;
private Timer _blinkTimer;
private int _timerCnt = 0;
//double buffer
private Graphics2D _bufferGraphics;
private Image _bufferImage = null;
private Thread _loop;
private long _delayTime = 10000L;
private GameStage (){
super(true);
setBackground(Color.black);
setPreferredSize( new Dimension(GameConstants.SCREEN_WIDTH, GameConstants.SCREEN_HEIGHT));
setExitShortCuts();
setFocusable(true);
requestFocus();
//setIgnoreRepaint(true);
_blinkTimer = new Timer(GameConstants.TIME_BETWEEN_FLASHES, this);
_delayTime = 1000/GameConstants.FPS;
_delayTime = TimeUnit.NANOSECONDS.convert(_delayTime, TimeUnit.MILLISECONDS);
}
public static GameStage getInstance () {
if (instance == null) {
instance = new GameStage();
instance.initInstance();
}
return instance;
}
public void stopGame () {
running = false;
}
private void initInstance () {
_gameData = GameData.getInstance();
_sprites = Sprites.getInstance();
_screenManager = ScreenManager.getInstance();
addKeyListener( new KeyAdapter() {
public void keyPressed(KeyEvent event) {
on_KeyDown(event);
}
public void keyReleased(KeyEvent event) {
on_KeyUp(event);
}
});
createStage();
}
public void flashStage (){
if (!_blinkTimer.isRunning()) {
_blinkTimer.restart();
blink();
}
}
public void newLevel () {
_player.upgrade();
}
//the main loop
public void run () {
long startTime, waitTime, elapsedTime;
running = true;
while(running) {
startTime = System.nanoTime();
if (_gameData.getGameMode() != GameConstants.PAUSE) {
_screenManager.updateScreen();
renderStage();
paintStage();
}
elapsedTime = System.nanoTime() - startTime;
waitTime = Math.max(_delayTime - elapsedTime, 5/10000L);
try {
TimeUnit.NANOSECONDS.sleep(waitTime);
} catch(InterruptedException e) {}
}
System.exit(0);
}
//action for our timer listener
public void actionPerformed(ActionEvent event) {
//if event comes from the timer
if (event.getSource() == _blinkTimer) {
blink();
}
}
public void showGameOver() {
_city = new City();
_ufo = new Ufo();
_blinkTimer.stop();
_blinkTimer.setDelay ( GameConstants.TIME_BETWEEN_GAME_OVER_FLASHES);
_blinkTimer.restart();
}
private void renderStage () {
if (_bufferImage == null){
_bufferImage = createImage(GameConstants.SCREEN_WIDTH, GameConstants.SCREEN_HEIGHT);
return;
} else {
_bufferGraphics = (Graphics2D) _bufferImage.getGraphics();
}
// clear the background
_bufferGraphics.setColor(Color.decode(_gameData.getBackgroundColor()));
_bufferGraphics.fillRect (0, 0, GameConstants.SCREEN_WIDTH, GameConstants.SCREEN_HEIGHT);
//paint all individual elements
if (_city != null) {
_city.draw(_bufferGraphics);
}
_screenManager.paintTopLayer(_bufferGraphics);
//draw gameInfo
_gameInfo.draw(_bufferGraphics);
}
private void paintStage () {
if (_bufferImage == null) return;
Graphics g;
try {
g = this.getGraphics();
if ((g != null) && (_bufferImage != null))
g.drawImage(_bufferImage, 0, 0, null);
g.dispose();
} catch (Exception e) {
System.out.println("error with graphics: " + e);
}
}
private void createStage () {
_city = new City();
_player = Player.getInstance();
_gameInfo = GameInfo.getInstance();
_loop = new Thread(this);
_loop.start();
}
private void blink () {
if (_gameData.getGameMode() == GameConstants.PLAY) {
if (_gameData.getBackgroundColor() == GameConstants.BACKGROUND_COLOR) {
_gameData.setBackgroundColor(GameConstants.BACKGROUND_FLASH_COLOR);
} else {
_gameData.setBackgroundColor(GameConstants.BACKGROUND_COLOR);
}
} else if (_gameData.getGameMode() == GameConstants.GAME_OVER) {
if (_gameData.getBackgroundColor() == GameConstants.BACKGROUND_COLOR_OVER_1) {
_gameData.setBackgroundColor(GameConstants.BACKGROUND_COLOR_OVER_2);
} else {
_gameData.setBackgroundColor(GameConstants.BACKGROUND_COLOR_OVER_1);
}
}
_timerCnt += 1;
// System.out.println ("blink stage");
//flash has ended
if (_timerCnt > GameConstants.NUMBER_OF_FLASHES) {
if (_gameData.getGameMode() == GameConstants.PLAY) {
_gameData.setBackgroundColor(GameConstants.BACKGROUND_COLOR);
_blinkTimer.stop();
_timerCnt = 0;
} /*else if (_gameData.getGameMode() == GameConstants.GAME_OVER) {
//continue blinking
//_blinkTimer.reset();
//blink();
}*/
}
}
private void setExitShortCuts () {
addKeyListener( new KeyAdapter() {
public void keyPressed(KeyEvent e){
int keyCode = e.getKeyCode();
if ((keyCode == KeyEvent.VK_ESCAPE) || (keyCode == KeyEvent.VK_Q) ||
(keyCode == KeyEvent.VK_END) ||
((keyCode == KeyEvent.VK_C) && e.isControlDown()) ) {
running = false;
}
}
});
}
//KEY EVENTS
private void on_KeyDown (KeyEvent event) {
switch (event.getKeyCode()) {
case KeyEvent.VK_LEFT:
_pressedLeft = true;
break;
case KeyEvent.VK_RIGHT:
_pressedRight = true;
break;
}
}
private void on_KeyUp (KeyEvent event) {
switch (event.getKeyCode()) {
case KeyEvent.VK_LEFT:
_pressedLeft = false;
break;
case KeyEvent.VK_RIGHT:
_pressedRight = false;
break;
case KeyEvent.VK_SPACE:
_player.shoot(_pressedLeft, _pressedRight);
break;
}
}
}

Java: GameInfo
drawing text
In GameInfo I need to draw a background and then the text for the score. The way I accomplish that is very different from the way I did it in Flash, primarily because Java has a separate method to draw text, but also because I used a sprite in Flash for the GameInfo and turned that into a bitmapData object.
So here, once again, I create a BufferedImage object, and I make it the right size. Then after I create its graphics context, I set its background color:
GraphicsEnvironment environment = GraphicsEnvironment.getLocalGraphicsEnvironment();
_graphicsConfig = environment.getDefaultScreenDevice().getDefaultConfiguration();
_buffer = _graphicsConfig.createCompatibleImage(GameConstants.SCREEN_WIDTH, 56);
_bufferGraphics = _buffer.createGraphics();
_bufferGraphics.setColor(Color.decode("#1A661B"));
The way this works is somewhat similar to the way it does in Flash. Once you set the color of a Graphics object, whatever you add to that object will be of that previously set color. So for instance:
Graphics2D myCanvas = image.createGraphics(); myCanvas.setColor(Color.green); Rectangle2D.Double rect = new Rectangle2D.Double(0,0,200,100); //I will draw a green rectangle myCanvas.fill(rect); myCanvas.setColor(Color.red); rect = new Rectangle2D.Double(10,10,120,80); //I will draw a red rectangle rect = new Rectangle2D.Double(20,20,100,60); myCanvas.fill(rect); myCanvas.setColor(Color.blue); //I will draw a blue rectangle myCanvas.fill(rect);
It doesn't have to be a rectangle, of course. It could be text:
public void update (String score) {
if (_buffer == null) return;
_bufferGraphics = _buffer.createGraphics();
_bufferGraphics.setColor(Color.decode("#1A661B"));
Rectangle2D.Double rect = new Rectangle2D.Double(0,0,GameConstants.SCREEN_WIDTH,56);
_bufferGraphics.fill(rect);
_bufferGraphics.setFont( new Font( "Serif", Font.BOLD, 40 ) );
_bufferGraphics.setColor(Color.decode("#FFC07C"));
_bufferGraphics.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
_bufferGraphics.drawString( score, 300, 38);
_bufferGraphics.dispose();
}
Here I fill the background with a green rectangle, and then I use drawString to draw the score, but using a different, yellowish, color this time for the text.
So the pattern is always: setColor, draw/fill, setColor, draw/fill...
The GameInfo Class
package atlantis;
import java.awt.*;
import java.awt.geom.*;
import java.awt.image.*;
public class GameInfo {
private static GameInfo instance;
private BufferedImage _buffer;
private Graphics2D _bufferGraphics;
private GraphicsConfiguration _graphicsConfig;
private GameInfo (){
GraphicsEnvironment environment = GraphicsEnvironment.getLocalGraphicsEnvironment();
_graphicsConfig = environment.getDefaultScreenDevice().getDefaultConfiguration();
_buffer = _graphicsConfig.createCompatibleImage(GameConstants.SCREEN_WIDTH, 56);
_bufferGraphics = _buffer.createGraphics();
_bufferGraphics.setColor(Color.decode("#1A661B"));
Rectangle2D.Double rect = new Rectangle2D.Double(0,0,GameConstants.SCREEN_WIDTH,56);
_bufferGraphics.fill(rect);
}
public static GameInfo getInstance () {
if (instance == null) {
instance = new GameInfo();
}
return instance;
}
public void update (String score) {
if (_buffer == null) return;
_bufferGraphics = _buffer.createGraphics();
_bufferGraphics.setColor(Color.decode("#1A661B"));
Rectangle2D.Double rect = new Rectangle2D.Double(0,0,GameConstants.SCREEN_WIDTH,56);
_bufferGraphics.fill(rect);
_bufferGraphics.setFont( new Font( "Serif", Font.BOLD, 40 ) );
_bufferGraphics.setColor(Color.decode("#FFC07C"));
_bufferGraphics.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
_bufferGraphics.drawString( score, 300, 38);
_bufferGraphics.dispose();
}
public void draw (Graphics2D graphics) {
if (_buffer == null || graphics == null) return;
//System.out.println(_bufferGraphics);
graphics.drawImage(_buffer, 0, 372, null);
//
}
}

Java: Enemy Ship
the heartless bastards!
The Ship class is the same as the one in Flash. Once again, I implement ActionListener for a Timer, in this case the ship uses a timer to determine when to start a new pass across the screen.
The Ship Class
package atlantis.elements;
import java.awt.event.*;
import atlantis.dynamic.*;
import atlantis.*;
import javax.swing.Timer;
public class Ship extends MovingSprite implements ActionListener {
private String _direction;
private AttackWave _wave;
private final int _lineIncrement = 42;
private int _line= 60;
private Timer _timer;
private Player _player;
public DeathRay ray;
public GameSprite target;
public Ship (String type, String direction, AttackWave wave) {
this.type = type;
active = false;
_direction = direction;
_wave = wave;
_manager.addBody(this);
_manager.addSprite(this);
_timer = new Timer(_gameData.getTimeBetweenPasses(), this);
vy = 0;
if (type == "ship_2") {
//faster ship
vx = _gameData.getShipSpeed() + 2;
} else {
vx = _gameData.getShipSpeed();
}
if (_direction == "fromRight") {
flipped = true;
vx *= -1;
}
positionShip();
_player = Player.getInstance();
}
public void start (){
active = true;
}
public void move () {
if (_gameData.getGameMode() != GameConstants.PLAY) return;
super.move();
if (ray != null) {
ray.x = x + (int)(getWidth()/2);
ray.y = y + getHeight();
if (!ray.active) ray.active = true;
}
//check if off screen
if (_direction == "fromLeft") {
if (x > GameConstants.SCREEN_WIDTH && active) {
active = false;
setNewPass();
}
} else {
if (x < -this.getWidth() && active) {
active = false;
setNewPass();
}
}
}
public void destroy () {
_wave.shipsKilled++;
removeShip();
_manager.removeBody(this);
//add explosion
if (type == "ship_2") {
//remove all active ships
_wave.clearSkies();
_manager.addSprite(new Boom(x - 10, y - 10, "boomXLarge"));
} else {
_manager.addSprite(new Boom(x, y, "boomLarge"));
}
}
public void actionPerformed (ActionEvent event) {
_timer.stop();
if (_gameData.getGameMode() != GameConstants.PLAY) return;
_line += _lineIncrement;
positionShip();
if (_line == 186) {
//set death ray
if (ray == null) {
//pick a target
target = _player.getTarget();
ray = new DeathRay(x + (int)(getWidth()/2), y + getHeight());
}
}
active = true;
}
public void removeShip () {
active = false;
if (_timer != null && _timer.isRunning()) {
_timer.stop();
_timer = null;
}
if (ray != null) {
ray.active = false;
_manager.removeSprite(ray);
}
_wave.removeShip();
}
private void positionShip () {
y = nextY = _line;
if (_direction == "fromLeft") {
flipped = false;
x = nextX = -60;
if (type == "ship2") x = nextX = -36;
} else {
flipped = true;
x = nextX = GameConstants.SCREEN_WIDTH;
}
}
private void setNewPass() {
if (_gameData.getGameMode() != GameConstants.PLAY) return;
//last line
if (_line >= 186) {
//last pass
removeShip();
} else {
if (!_timer.isRunning()) _timer.restart();
}
}
}

Java: The AttackWave
senseless violence inside a loop
In the AttackWave I have a couple of things worth mentioning regarding arrays. Arrays in Java have a finite length, and that must be determined when constructing the Array:
private String[] _pattern = {"ship_1","ship_3","ship_1","ship_2","ship_3"};
This means you can't push an item into an array, but only change one of its indexes. For elastic collections you must use other collection types like ArrayList.
Speaking of collections, the Java Collections class have a number of very useful static methods to help you with arrays, array lists, vectors... I use one here to shuffle the pattern array.
Collections.shuffle(Arrays.asList(_pattern));
Syntax
The Java Array:
private String[] _pattern = {"ship_1","ship_3","ship_1","ship_2","ship_3"};You must declare the type of object listed in the array, which is String in the previous example. But you don't use new Array to create this object. That doesn't exist in Java.
The Array class in Java is not an Array but a series of static methods used to manage an object of type []. Here is an example of how to use the Array class to set/change a value, to accomplish the same thing done in the first line:
_pattern[0] = someValue;Array.set(_pattern, 0, someValue);
The AttackWave Class
package atlantis.elements;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Arrays;
import javax.swing.Timer;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
import atlantis.*;
public class AttackWave implements ActionListener {
private ArrayList _ships;
private ArrayList _shipsAlive;
private String[] _pattern = {"ship_1","ship_3","ship_1","ship_2","ship_3"};
private GameData _gameData;
private Timer _timer;
private int _timerCnt = 0;
private String _lastDirection = "";
public boolean done = false;
public int shipsKilled = 0;
public int totalShips;
public AttackWave () {
_gameData = GameData.getInstance();
_ships = new ArrayList();
_shipsAlive = new ArrayList();
totalShips = _gameData.getNumShips();
//shuffle the pattern
Collections.shuffle(Arrays.asList(_pattern));
Ship ship;
int shipType = 0;
//create ships
for (int i = 0; i < totalShips; i++) {
ship = new Ship (_pattern[shipType], setDirection(), this);
shipType++;
if (shipType == _pattern.length) shipType = 0;
_ships.add(ship);
}
_timer = new Timer(_gameData.getTimeBetweenShips(), this);
}
public void start () {
startShip(0);
_timer.start();
}
public void removeShip () {
totalShips--;
System.out.println("REMOVE SHIP");
if (totalShips <= 0) {
if(_timer != null && _timer.isRunning()) {
_timer.stop();
_timer = null;
}
GameController.getInstance().newLevel();
}
}
public void clearSkies () {
Ship ship;
for (int i = _shipsAlive.size() - 1; i >= 0; i--) {
ship = _shipsAlive.get(i);
if (ship.active) {
ship.removeShip();
shipsKilled++;
_gameData.setScore (_gameData.getScore() + GameConstants.ENEMY_POINTS);
_shipsAlive.remove(i);
}
}
}
public void actionPerformed (ActionEvent event) {
_timerCnt++;
if (_timerCnt > _ships.size()-1) {
_timer.stop();
done = true;
_timer = null;
return;
}
startShip(_timerCnt);
}
private String setDirection () {
String direction;
if (_lastDirection != "") {
if (_lastDirection == "fromLeft") {
if (Math.random() > 0.7) {
direction = "fromLeft";
} else {
direction = "fromRight";
}
} else {
if (Math.random() > 0.7) {
direction = "fromRight";
} else {
direction = "fromLeft";
}
}
} else {
if (Math.random() > 0.5) {
direction = "fromLeft";
} else {
direction = "fromRight";
}
}
_lastDirection = direction;
return direction;
}
private void startShip (int index) {
try {
_ships.get(index).start();
_shipsAlive.add(_ships.get(index));
} catch (IndexOutOfBoundsException e) {
System.out.println("No more ships in this array. Bad, Bad mistake!");
}
}
}

Java: GameData
getters and setters coming out of the wazoo.
I like to use getters and setters even when the use of public variables could save me some lines of code. The main reason is that I can always come back later and add some extra logic to how a value is set or returned.
But in Java, getters and setters for properties that don't require extra logic are even more pointless. So I would recommend using public variables here.
Getters and setters in Java are nothing more than a coding convention: the use of the word get and set in the methods that update a property.
Other than that, the only noticeable difference here is that hex values for color are stored as Strings. If you will remember from GameStage, or GameInfo, the string is decoded through a static method in the Color class.
The GameData Class
package atlantis;
public class GameData {
private static GameData instance;
private GameData (){}
public static GameData getInstance () {
if (instance == null) {
instance = new GameData();
}
return instance;
}
private String _gameMode;
private int _numBullets = 0;
private int _numShips = 4;
private int _score = 0;
private int _timeBetweenShips = 2000;
private int _timeBetweenPasses = 1000;
private int _shipSpeed = 10;
private String _backgroundColor;
public String getGameMode () {
return _gameMode;
}
public void setGameMode (String value) {
_gameMode = value;
}
/*public int geLevel () {
return _level;
}
public void setLevel (int value) {
_level = value;
}
*/
public int getScore () {
return _score;
}
public void setScore (int value){
_score = value;
GameInfo.getInstance().update(String.valueOf(_score));
}
public int getNumBullets () {
return _numBullets;
}
public void setNumBullets (int value){
if (value > GameConstants.NUM_BULLETS_ON_SCREEN) return;
_numBullets = value;
}
public void setNumShips (int value){
_numShips = value;
}
public int getNumShips () {
return _numShips;
}
public void setTimeBetweenShips (int value){
_timeBetweenShips = value;
}
public int getTimeBetweenShips () {
return _timeBetweenShips;
}
public void setTimeBetweenPasses (int value){
_timeBetweenPasses = value;
}
public int getTimeBetweenPasses () {
return _timeBetweenPasses;
}
public void setShipSpeed (int value){
_shipSpeed = value;
}
public int getShipSpeed () {
return _shipSpeed;
}
public String getBackgroundColor () {
if (_backgroundColor == null) _backgroundColor = GameConstants.BACKGROUND_COLOR;
return _backgroundColor;
}
public void setBackgroundColor (String value){
_backgroundColor = value;
}
public void reset (){
_backgroundColor = GameConstants.BACKGROUND_COLOR;
}
public void newLevel (){
reset();
_numShips += 2;
_timeBetweenShips -= 50;
_timeBetweenPasses -= 50;
_shipSpeed += 1;
if (_timeBetweenShips < 300) _timeBetweenShips = 300;
if (_timeBetweenPasses < 200) _timeBetweenPasses = 200;
}
}

Java: Minor Stuff
the rest of it
Here I list the remaining classes for the game elements. And I will do my best to explain rotation in Java. If you remember, it wasn't exactly the simplest thing to do in Flash either. In Java, it gets worse.
Just to remind you why we need this: I decided to have the art for the two frames in the death ray animation appear horizontally in the source png instead of vertically, which is the way it appears on screen. I did this to save space in the png and so make it smaller.
I could have drawn the thing as a square, and then tile it. In fact I have the code to do that here, commented out because it would require a different source drawing.
But here is the code:
public void draw (Graphics2D g){
Rectangle clipping = this.sourceRectangle();
if (clipping == null) return;
if (_clip1 == null) {
_clip1 = clipping;
} else if (_clip2 == null) {
_clip2 = clipping;
}
if (_clip1 == clipping && _img1 != null) {
g.drawImage(_img1, x, y, null);
return;
}
if (_clip2 == clipping && _img2 != null) {
g.drawImage(_img2, x, y, null);
return;
}
//if image is not stored already, process it, which means, rotate it
BufferedImage original= new BufferedImage(clipping.width, clipping.height, BufferedImage.TYPE_INT_ARGB);
Graphics2D og = original.createGraphics();
og.drawImage(Sprites.IMG_SOURCE, 0, 0, clipping.width, clipping.height, clipping.x, clipping.y, clipping.x + clipping.width, clipping.y + clipping.height, null);
BufferedImage newimg = new BufferedImage(clipping.height, clipping.width, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2d = newimg.createGraphics();
g2d.rotate(Math.PI / 2, original.getWidth() / 2, original.getHeight() / 2);
g2d.drawImage(original, null,
original.getWidth() / 2 - newimg.getWidth() / 2,
(original.getHeight() / 2 - newimg.getHeight() / 2) * -1);
g.drawImage(newimg, x, y, null);
if (_clip1 == clipping) {
_img1 = newimg;
} else {
_img2 = newimg;
}
//or simply make a new and smaller clip and stack them one on top of the other
/*Rectangle clipping = this.getImageClipping();
if (clipping == null) return;
g.drawImage(Sprites.IMG_SOURCE,
x,y, x + clipping.width, y+clipping.height,
clipping.x, clipping.y, clipping.x + clipping.width, clipping.y + clipping.height
, null);
for (int i = 1; i < 18; i++) {
g.drawImage(Sprites.IMG_SOURCE,
x,y+i*clipping.height, x + clipping.width, y+(i+1)*clipping.height,
clipping.x, clipping.y, clipping.x + clipping.width, clipping.y + clipping.height
, null);
}
*/
}
}
I once again store the image for both frames the first time I build them, so I don't have to run the rotation code more than twice (one per each frame.)
But the way rotation works in Java is this:
You create a blank image. Then you rotate the graphics context of that image. Think of it as building a canvas and then rotating it.
Now whatever you draw on this canvas will appear upside down, and this is the rotation logic in Java. You rotate a canvas and then you draw something on it. It will automatically "appear" rotated. This is when problems start.
The rotated canvas is still a rectangle. But the rotated image might not fill it completely now. It might get clipped in some parts, and show the background of the canvas in other parts. Like in this example:
Original Image
Image copied to a rotated canvas
Even when turning something upside down, or on its side, I have to be careful with the changes to registration point.
I must confess I tested this code over and over again, to get the right results and in the end of the process I couldn't even understand what I was doing. The logic is this:
BufferedImage original= new BufferedImage(clipping.width, clipping.height, BufferedImage.TYPE_INT_ARGB); Graphics2D og = original.createGraphics(); og.drawImage(Sprites.IMG_SOURCE, 0, 0, clipping.width, clipping.height, clipping.x, clipping.y, clipping.x + clipping.width, clipping.y + clipping.height, null); BufferedImage newimg = new BufferedImage(clipping.height, clipping.width, BufferedImage.TYPE_INT_ARGB); Graphics2D g2d = newimg.createGraphics(); g2d.rotate(Math.PI / 2, original.getWidth() / 2, original.getHeight() / 2); g2d.drawImage(original, null, original.getWidth() / 2 - newimg.getWidth() / 2, (original.getHeight() / 2 - newimg.getHeight() / 2) * -1); g.drawImage(newimg, x, y, null);
So I start by grabbing the original art and drawing it to a buffered image.
I create a new buffered image, with the rotated dimensions, meaning I use the original height for width, and the original width for height.
I rotate the graphics context of that new image. The method receives the angle of rotation and the center point. So I rotate 90 degrees (from horizontal to vertical) and use the center of the image as the center of rotation.
Next, I use yet another overloaded drawImage, this one from Graphics2D. It receives one registration point, and the null is for something called BufferedImageOp, which is not important here. But it is the closest thing to using a Matrix. The BufferedImageOp is a rendering operation you define before drawing the image, and so the image is drawn with that operation applied to it, just like Flash does with Matrix. But what I need here is a null rendering operation and a new registration point.
As you can see, I must bring the registration of the image back to Top Left, but now it is the top left of the rotated canvas! At least that's what I think I'm doing there.
Alternatively I could use a different method, and tile a square image until I have the perfect vertical death ray:
Rectangle clipping = this.getImageClipping();
if (clipping == null) return;
g.drawImage(Sprites.IMG_SOURCE,
x,y, x + clipping.width, y+clipping.height,
clipping.x, clipping.y, clipping.x + clipping.width, clipping.y + clipping.height
, null);
for (int i = 1; i < 18; i++) {
g.drawImage(Sprites.IMG_SOURCE,
x,y+i*clipping.height, x + clipping.width, y+(i+1)*clipping.height,
clipping.x, clipping.y, clipping.x + clipping.width, clipping.y + clipping.height
, null);
}
This too would need to be done only once per frame. But rotation might be the only option in some cases.
The DeathRay Class
package atlantis.elements;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.image.BufferedImage;
import atlantis.dynamic.GameSprite;
public class DeathRay extends GameSprite {
private BufferedImage _img1;
private BufferedImage _img2;
private Rectangle _clip1;
private Rectangle _clip2;
public DeathRay (int _x, int _y) {
x = _x;
y = _y;
type = "deathRay";
active = false;
_manager.addSprite(this);
}
public void destroy () {
super.destroy();
_manager.removeSprite(this);
active = false;
}
public void draw (Graphics2D g){
Rectangle clipping = this.sourceRectangle();
if (clipping == null) return;
if (_clip1 == null) {
_clip1 = clipping;
} else if (_clip2 == null) {
_clip2 = clipping;
}
if (_clip1 == clipping && _img1 != null) {
g.drawImage(_img1, x, y, null);
return;
}
if (_clip2 == clipping && _img2 != null) {
g.drawImage(_img2, x, y, null);
return;
}
//if image is not stored already, process it, which means, rotate it
BufferedImage original= new BufferedImage(clipping.width, clipping.height, BufferedImage.TYPE_INT_ARGB);
Graphics2D og = original.createGraphics();
og.drawImage(Sprites.IMG_SOURCE, 0, 0, clipping.width, clipping.height, clipping.x, clipping.y, clipping.x + clipping.width, clipping.y + clipping.height, null);
BufferedImage newimg = new BufferedImage(clipping.height, clipping.width, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2d = newimg.createGraphics();
g2d.rotate(Math.PI / 2, original.getWidth() / 2, original.getHeight() / 2);
g2d.drawImage(original, null,
original.getWidth() / 2 - newimg.getWidth() / 2,
(original.getHeight() / 2 - newimg.getHeight() / 2) * -1);
g.drawImage(newimg, x, y, null);
if (_clip1 == clipping) {
_img1 = newimg;
} else {
_img2 = newimg;
}
//or simply make a new and smaller clip and stack them one on top of the other
/*Rectangle clipping = this.getImageClipping();
if (clipping == null) return;
g.drawImage(Sprites.IMG_SOURCE,
x,y, x + clipping.width, y+clipping.height,
clipping.x, clipping.y, clipping.x + clipping.width, clipping.y + clipping.height
, null);
for (int i = 1; i < 18; i++) {
g.drawImage(Sprites.IMG_SOURCE,
x,y+i*clipping.height, x + clipping.width, y+(i+1)*clipping.height,
clipping.x, clipping.y, clipping.x + clipping.width, clipping.y + clipping.height
, null);
}
*/
}
}
The Boom Class
package atlantis.elements;
import java.awt.image.BufferedImage;
import java.awt.Rectangle;
import atlantis.dynamic.GameSprite;
public class Boom extends GameSprite {
public Boom (int _x, int _y, String _type) {
x = _x;
y = _y;
type = _type;
}
public Rectangle sourceRectangle () {
Rectangle data = _sprites.getImage(this);
//if data is null, we have reached end of animation cycle
if (data == null) {
//remove sprite from the stage bitmap
_manager.removeSprite(this);
}
return data;
}
}
The Ufo Class
package atlantis.elements;
import atlantis.dynamic.MovingSprite;
public class Ufo extends MovingSprite {
public Ufo () {
type = "ufo";
x = nextX = 50;
y = nextY = 300;
_manager.addBody(this);
_manager.addSprite(this);
vy = -3;
vx = 3;
}
public void move () {
super.move();
//
//check if off screen
if (y < -30) {
active = false;
_manager.removeBody(this);
}
}
}

