Here you'll find a selection of beginners Java gaming tutorials.
This tutorial can be discussed here
This tutorial can be discussed here
Disclaimer: This tutorial is provided as is. I don't guarantee that the provided source is perfect or that that it provides best practices.
Context highlighted source is also available here:
Game.java
SpriteStore.java
Sprite.java
Entity.java
ShipEntity.java
ShotEntity.java
AlienEntity.java
Disclaimer: This tutorial is provided as is. I don't guarantee that the provided source is perfect or that that it provides best practices.
For our space invaders game we're going to have a main window. The window needs to use acceleated graphics. It also needs to respond to the player's key presses to move our player's ship around. For now we can call this class Game since it represents our main game.
In that window we want to see some things moving around. The player's ship, the aliens and the shots that the players fire. Since all of these things have common properites (i.e. they display a graphic and move around the screen) we can infer a common class to represent each one with potentially subclasses to define the specific behaviours of these different types. Since "thing" is such a terrible name for a class, for our design I'm going to call them Entities. From this we get 4 classes. Entity with 3 subclasses, ShipEntity, AlienEntity and ShotEntity
Finally for each Entity we have we'd like to have an image displayed, using an old term, a Sprite. However, we might use the same Sprite for multiple entities, for instance the aliens. It seems logically therefore to keep the sprite as a seperate object from the entity. In addition we don't want to waste graphics memory so we'd like to only load each sprite once. To manage this it'd be nice to add a class to manage loading of the Sprites and storing them for future use. So we add a pair of classes to our design, Sprite and SpriteStore.
Our basic window is going to be created and maintained by a central class, Game. The following sections cover the initial sections of code in the main class.
In java our entry point is "public static void main(String arg[])". This is where the application starts when its run. From here we're going to create an instance of our main class which will start everything else running. Game will be a subclass of Canvas, since it will be the main element displaying the graphics. Note, that it needs to be a subclass of Canvas since its one of the only components that supports using accelerated graphics.
public static void main(String argv[]) { Game g = new Game(); g.gameLoop(); }
First we need to create our window and configure its contents. We're going to fix our resolution to 800x600. However, since the window may have decoration the content must be set to 800x600 and we must rely on pack() (shown a little later) to actually size the window appropriately.
// create a frame to contain our game
JFrame container = new JFrame("Space Invaders 101");
// get hold the content of the frame and set up the
// resolution of the game
JPanel panel = (JPanel) container.getContentPane();
panel.setPreferredSize(new Dimension(800,600));
panel.setLayout(null);
// setup our canvas size and put it into the content of the frame
setBounds(0,0,800,600);
panel.add(this);
Since the canvas we're working with is going to be actively redrawn (i.e. accelerated graphics) we need to prevent Java AWT attempting to redraw our
surface. Finally, we get the window to resolve its size, prevent the
user resizing it and make it visible.
// Tell AWT not to bother repainting our canvas since we're // going to do that our self in accelerated mode setIgnoreRepaint(true); // finally make the window visible container.pack(); container.setResizable(false); container.setVisible(true);
To manage our accelerated graphics canvas we're going to rely on a class provided from the JDK. The BufferStrategy is named that because its a strategy for managing buffers, or rather the swapping of buffers. This supports us using page flipping and accelerated graphics.
Creating a BufferStrategy couldn't be simpler. We simply ask the Canvas to do it for us. The only thing that needs to be specified is how many buffers to use to manage the screen, in this case we're going to use just 2.
// create the buffering strategy which will allow AWT // to manage our accelerated graphics createBufferStrategy(2); strategy = getBufferStrategy();
The game loop is a remarkably important part of any game. Client side games tend to be single threaded. This helps prevent a whole bunch of complexities synchronising game updates and game drawing. Generally this results in more stable and maintainble code. There are good arguments to use threading, however for the purposes of this tutorial we're not going to consider it.
The normal game loop runs something like this:
At this stage we're just interested in getting the screen swap and timing done, so we add a function called gameLoop that does this:
while (gameRunning) {
// work out how long its been since the last update, this
// will be used to calculate how far the entities should
// move this loop
long delta = System.currentTimeMillis() - lastLoopTime;
lastLoopTime = System.currentTimeMillis();
// Get hold of a graphics context for the accelerated
// surface and blank it out
Graphics2D g = (Graphics2D) strategy.getDrawGraphics();
g.setColor(Color.black);
g.fillRect(0,0,800,600);
// finally, we've completed drawing so clear up the graphics
// and flip the buffer over
g.dispose();
strategy.show();
// finally pause for a bit. Note: this should run us at about
// 100 fps but on windows this might vary each loop due to
// a bad implementation of timer
try { Thread.sleep(10); } catch (Exception e) {}
}
Finally we call gameLoop from the bottom of the constructor of Game. This just starts the game loop running around. If you've been following this code you should be able to run what you currently have and a window should be displayed with an accelerated canvas that shows a black screen.
So heres the sprite class. Essentially it just takes and holds an image which can be drawn at a specified location onto a graphics context.
public class Sprite {
/** The image to be drawn for this sprite */
private Image image;
/**
* Create a new sprite based on an image
*
* @param image The image that is this sprite
*/
public Sprite(Image image) {
this.image = image;
}
/**
* Get the width of the drawn sprite
*
* @return The width in pixels of this sprite
*/
public int getWidth() {
return image.getWidth(null);
}
/**
* Get the height of the drawn sprite
*
* @return The height in pixels of this sprite
*/
public int getHeight() {
return image.getHeight(null);
}
/**
* Draw the sprite onto the graphics context provided
*
* @param g The graphics context on which to draw the sprite
* @param x The x location at which to draw the sprite
* @param y The y location at which to draw the sprite
*/
public void draw(Graphics g,int x,int y) {
g.drawImage(image,x,y,null);
}
}
Implementing this in our sprite store looks like this:
/** The single instance of this class */
private static SpriteStore single = new SpriteStore();
/**
* Get the single instance of this class
*
* @return The single instance of this class
*/
public static SpriteStore get() {
return single;
}
The "single" is the single instance of the class ever created. The static get method gives us access to the single instance.
The first step is locate the sprite:
URL url = this.getClass().getClassLoader().getResource(ref);
Its important to note that the chain of functions used to get to the class is only there to support specialised class loaders (like the one found in WebStart). The retrieved URL will point to the image specified in the string "ref" relative to the classpath.
The next step is to actually load in the image. In Java this is a simple matter of using the utlity class ImageIO. To load our image we use this code:
sourceImage = ImageIO.read(url);
Finally, we need to allocate some accelerated graphics memory to store our image in. This will allow the image to be drawn without the CPU getting involved, we just want the graphics card to do the work for us.
Allocating the graphics memory is achieved like so:
// create an accelerated image of the right size to store our sprite in
GraphicsConfiguration gc = GraphicsEnvironment.getLocalGraphicsEnvironment().
getDefaultScreenDevice().getDefaultConfiguration();
Image image = gc.createCompatibleImage(sourceImage.getWidth(),
sourceImage.getHeight(),
Transparency.BITMASK);
The final step is to draw our loaded image into our accelerated graphics image. This essentially creates our sprite:
// draw our source image into the accelerated image image.getGraphics().drawImage(sourceImage,0,0,null);
The only thing we have left to do is create our actual Sprite object.
// create a sprite, add it the cache then return it Sprite sprite = new Sprite(image); sprites.put(ref,sprite);
Likewise, whenever a sprite is requested we check whether its already in the map. If it is, we return our cached copy instead of loading a new copy:
// if we've already got the sprite in the cache
// then just return the existing version
if (sprites.get(ref) != null) {
return (Sprite) sprites.get(ref);
}
// cycle round asking each entity to move itself
for (int i=0;i<entities.size();i++) {
Entity entity = (Entity) entities.get(i);
entity.move(delta);
}
// cycle round drawing all the entities we have in the game
for (int i=0;i<entities.size();i++) {
Entity entity = (Entity) entities.get(i);
entity.draw(g);
}
Rememebr we calculated how long it'd been since we looped round. This can be used to work out how far an entity should move on the current loop. Right, now we know what Entity needs to be able to do, on to the implementation.
Once these properties are defined we can cover the two methods that we require of Entity. The move() method looks like this:
public void move(long delta) {
// update the location of the entity based on move speeds
x += (delta * dx) / 1000;
y += (delta * dy) / 1000;
}
Simple! We take how every much time has passed, multiply it by the movement in each direction and add this on to the location. The division by 1000 is to adjust for the fact that the movement value is specified in pixels per second, but the time is specified in milliseconds. Each loop all the entities will be moved in accordance with their currently movement values (velocities).
Next we need to be able to draw an Entity onto our accelerated graphics context. draw() is implemented like this:
public void draw(Graphics g) {
sprite.draw(g,(int) x,(int) y);
}
Essentially, this just draws the sprite onto the supplied graphics context at its current location. So each loop, the entity moves and is then redrawn at the right location.
Now we've defined our basic entity we should create a few subclasses as placeholders for some more functionality we'll add later on. We simply need to create the 3 subclasses of Entity; ShipEntity, ShotEntity and AlienEntity. For now we won't bother adding anything extra but it normally pays to be aware and add these things up front.
The final step is to create our entities and add them to the game world. If we add a utility method to central Game class called initEntities(). This will initialise a set of entities at the game start. The current implementation looks like this:
private void initEntities() {
// create the player ship and place it roughly in the center of the screen
ship = new ShipEntity(this,"sprites/ship.gif",370,550);
entities.add(ship);
// create a block of aliens (5 rows, by 12 aliens, spaced evenly)
alienCount = 0;
for (int row=0;row<5;row++) {
for (int x=0;x<12;x++) {
Entity alien = new AlienEntity(this,
"sprites/alien.gif",
100+(x*50),
(50)+row*30);
entities.add(alien);
alienCount++;
}
}
}
As you can see, the initialisation takes two steps. The first is to create the player's ship. We simply create a ShipEntity with the appropriate graphic and center it at the bottom of our canvas.
The second step is to create all the aliens. We loop through creating a block of aliens. Again each alien is just the creation of the AlienEntity positioned at the right location. In addition we count how many aliens we've created so we can track whether the player has won the game.
Assuming the code to support moving and displaying the entities has been added to main game loop, running the game should now show the player's ship and a bunch of aliens.
Now each type of Entity moves in its own way and with its own contraints. Lets look at each one.
public void move(long delta) {
// if we're moving left and have reached the left hand side
// of the screen, don't move
if ((dx < 0) && (x < 10)) {
return;
}
// if we're moving right and have reached the right hand side
// of the screen, don't move
if ((dx > 0) && (x > 750)) {
return;
}
super.move(delta);
}
What we essentially are saying here is that if we're moving left and we're about to move off the left hand side of the screen then don't allow the movement (i.e. return). In reverse if we're moving to the right and are about to move off the right hand side of the screen then don't allow the movement. Otherwise we just do the normal Entity movement routine.
The shot entity is pretty simple, it just wants to run up the screen until it either hits an alien (see Collision later on) or runs off the top of the screen, at which point we'd like to remove it from the entity list (for details of the remove entity method check out the source).
To start the shot moving with initialise the vertical movement to a negative number based on the speed we'd like the shot to move. The movement method itself looks like this:
public void move(long delta) {
// proceed with normal move
super.move(delta);
// if we shot off the screen, remove ourselfs
if (y < -100) {
game.removeEntity(this);
}
}
Simply put, if the shot moves off the top of the screen, remove it.
Aliens are the most tricky part of our space invaders game. As they move around we need to notice when they hit the side of the screen and start them moving in the opposite direction. In conjuction each time they change direction we'd like them all to move down a step. Part of this will be covered in the movement routine and part in the game logic. Game logic is used in this case since we need to first detect that the aliens should change direction then change them all (rather than a localised change like the other entities)
Hopefully, we now know what we want to do, so how? First we initialise the movement of the aliens to start them moving to the left based on the predefined speed. Next we put the detection of an alien hitting the side in the movement routine like this:
public void move(long delta) {
// if we have reached the left hand side of the screen and
// are moving left then request a logic update
if ((dx < 0) && (x < 10)) {
game.updateLogic();
}
// and vice vesa, if we have reached the right hand side of
// the screen and are moving right, request a logic update
if ((dx > 0) && (x > 750)) {
game.updateLogic();
}
// proceed with normal move
super.move(delta);
}
In the same way as in ShipEntity, we check whether the entity has hit the edge of the screen. However, in this case we notify the game that the game logic for all entities need to be run. We'll make this logic adapt the movement of the aliens but more on this later.
private class KeyInputHandler extends KeyAdapter {
public void keyPressed(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_LEFT) {
leftPressed = true;
}
if (e.getKeyCode() == KeyEvent.VK_RIGHT) {
rightPressed = true;
}
if (e.getKeyCode() == KeyEvent.VK_SPACE) {
firePressed = true;
}
}
public void keyReleased(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_LEFT) {
leftPressed = false;
}
if (e.getKeyCode() == KeyEvent.VK_RIGHT) {
rightPressed = false;
}
if (e.getKeyCode() == KeyEvent.VK_SPACE) {
firePressed = false;
}
}
public void keyTyped(KeyEvent e) {
// if we hit escape, then quit the game
if (e.getKeyChar() == 27) {
System.exit(0);
}
}
}
This class simply picks up keys being pressed and released and records their states in a set of boolean variables in the main class. In addition it checks for escape being pressed (which in our case exits the game)
To make this class work we need to add it to our canvas as a "KeyListener". This is done by adding the following line to the constructor of our Game class:
addKeyListener(new KeyInputHandler());
With this source code in place the boolean flags will be set to appropriate values as the keys are pressed. The next step is to respond to these boolean flags being set in the game loop. If we add this in the game loop we can add keyboard control to the ship:
// resolve the movement of the ship. First assume the ship
// isn't moving. If either cursor key is pressed then
// update the movement appropraitely
ship.setHorizontalMovement(0);
if ((leftPressed) && (!rightPressed)) {
ship.setHorizontalMovement(-moveSpeed);
} else if ((rightPressed) && (!leftPressed)) {
ship.setHorizontalMovement(moveSpeed);
}
As you can see we check if left or right is pressed. If they are we update the movement values associated with the player's ship. Hence when we hit the entity movement code the ship will move left or right.
If the player holds the fire key we'd like the ship to fire a shot up towards the aliens. However, we don't want to let the player just keep firing. It'd be good to limit how often they can fire. We'll put this functionality in a utility method called (somewhat logically) "tryToFire()"
// if we're pressing fire, attempt to fire
if (firePressed) {
tryToFire();
}
To prevent the player firing too often we'll record the time whenever they take a shot and prevent them taking a shot if the last shot was less than a set interval ago. Hmm, sounds complicated, its not! really! Here it is:
public void tryToFire() {
// check that we have waiting long enough to fire
if (System.currentTimeMillis() - lastFire < firingInterval) {
return;
}
// if we waited long enough, create the shot entity, and record the time.
lastFire = System.currentTimeMillis();
ShotEntity shot = new ShotEntity(this,"sprites/shot.gif",ship.getX()+10,ship.getY()-30);
entities.add(shot);
}
First we check if the last time the player took a shot was long enough ago. If it wasn't we don't bother firing and just return. If it was we record the current time. Next we create and add an entity to represent the shot fired by the player. Since our shot is setup to move up the screen it shoots off from the player towards the aliens.
First, we'll need to implement a check to resolve whether two entities have in fact collided. We'll do this in the Entity class like this:
public boolean collidesWith(Entity other) {
me.setBounds((int) x,(int) y,sprite.getWidth(),sprite.getHeight());
him.setBounds((int) other.x,(int) other.y,other.sprite.getWidth(),other.sprite.getHeight());
return me.intersects(him);
}
This method checks if the entity itself collides with the other entity specified. We're going to rely on rectangular intersection regions and the AWT class Rectangle. In this case the variables "me" and "him" are instances of Rectangle that are held at the class level.
First we configure the two rectangles to represent the two entities. Next we use the inbuilt functionality of java.awt.Rectangle to check if the two entities intersect with each other. This isn't the smartest way to do this by any means, but for our purposes it will be good enough.
The next thing we'll add is a way to notify entities that they have collided with another. To do this we'll add a method like this to the Entity class:
public abstract void collidedWith(Entity other);
Its been made abstract since different implementations of the Entity class will want to respond to collisions in their own ways, e.g. Alien<->Shot, Ship<->Alien.
public void collidedWith(Entity other) {
// if its an alien, notify the game that the player
// is dead
if (other instanceof AlienEntity) {
game.notifyDeath();
}
}
Again, the result of the collision is based on the game logic (covered later). If the entity that the ship collided with is an Alien then notify the game that player should die.
public void collidedWith(Entity other) {
// if we've hit an alien, kill it!
if (other instanceof AlienEntity) {
// remove the affected entities
game.removeEntity(this);
game.removeEntity(other);
// notify the game that the alien has been killed
game.notifyAlienKilled();
}
}
If the shot hit and alien then the alien and shot are removed. In addition the game logic is notified that the an alien has been killed (covered later).
// brute force collisions, compare every entity against
// every other entity. If any of them collide notify
// both entities that the collision has occured
for (int p=0;p<entities.size();p++) {
for (int s=p+1;s<entities.size();s++) {
Entity me = (Entity) entities.get(p);
Entity him = (Entity) entities.get(s);
if (me.collidesWith(him)) {
me.collidedWith(him);
him.collidedWith(me);
}
}
}
For each entity we cycle through all the other entities that we haven't compared against and check whether a collision has occured. If a collision has occured we notify both sides.
A smarter way to do this might be to check for the collisions only every so often. It might also be nice to have a flag on an entity whether it should detect collisions.
public void notifyAlienKilled() {
// reduce the alient count, if there are none left, the player has won!
alienCount--;
if (alienCount == 0) {
notifyWin();
}
// if there are still some aliens left then they all need to get faster, so
// speed up all the existing aliens
for (int i=0;i<entities.size();i++) {
Entity entity = (Entity) entities.get(i);
if (entity instanceof AlienEntity) {
// speed up by 2%
entity.setHorizontalMovement(entity.getHorizontalMovement() * 1.02);
}
}
}
In addition to recording if all the aliens have been killed we also speed the aliens up a bit. Every time an alien is killed the rest speed up by 2%.
The last step we need is to support entity based game logic. We implied the requirement when building the AlienEntity. When a single alien detects the edge of the screen we want all the aliens to change direction. To support this we're going to add two sections of code.
Each entity should be allowed to support its own logic. To facilitate this we going to add a method to the Entity class. doLogic() will allow subclasses of Entity to define a bit of logic that will be run whenever game logic is requested (see below). In AlienEntity for instance we request that game logic be run when the edge of the screen is detected. The doLogic() implementation in AlienEntity looks like this:
public void doLogic() {
// swap over horizontal movement and move down the
// screen a bit
dx = -dx;
y += 10;
// if we've reached the bottom of the screen then the player
// dies
if (y > 570) {
game.notifyDeath();
}
}
So, when an alien detects the edge of the screen it signals that game logic should be run on entities. The alien entities will change direction (dx = -dx) and move down the screen a bit (y += 10). Finally, if the alien has moved off the bottom of the screen then notify the game that the player is dead.
To complete the game logic at the entity level we need to add a method on the Game class that will indicate that the entity game logic should be run. First, we add this method on Game:
public void updateLogic() {
logicRequiredThisLoop = true;
}
This flag indicates that in the game loop we should run the logic associated with every entity currently in the game. To achieve this we add this in the game loop:
// if a game event has indicated that game logic should
// be resolved, cycle round every entity requesting that
// their personal logic should be considered.
if (logicRequiredThisLoop) {
for (int i=0;i<entities.size();i++) {
Entity entity = (Entity) entities.get(i);
entity.doLogic();
}
logicRequiredThisLoop = false;
}
If the flag is set, then cycle round the entities calling the doLogic() method on each. Finally, reset the flag so the logic doesn't automatically get run next loop.
Hopefully this tutorial has been of some help. If you look through the provided source code you'll find a selection of additions which complete the game more fully. These arn't covered in the tutorial because of their intricacy. However, the comments in the code should make the extra bits and pieces easy to understand.
If you have any comments or corrects feel free to mail me here
The following extensions of the game may help in understanding how game hangs together.
Add a frame rate counter to the screen while the game is playing. This could be done by counting game loops and recording the number counted each second. This value could then be displayed as part of the drawing section of the game loop.
Animating the aliens would require updating the Entity class to support multiple sprites. Then depending on the time passed the sprite being drawn for the entity could be swapped.
With the addition of the a new Entity the aliens could be made to fire back. This could either be done by timing it so each alien fires back every so many seconds. Alternatively, this could be based on a random occurance, say the alien has a 1 in a 100 chance of fireing back at any given moement.
Make it possible to pause the game by pressing a key (P?). This should pause all game updates but still continue to render the screen and the entities within the game. It might be nice to add a message indicating the game is paused.
Tutorial and Source written by Kevin Glass
Game sprites provided by Ari Feldman
A large number of people over at the Java Gaming Forums
The result of this tutorial can be seen here. The complete source for the tutorial can be found here. Its intended that you read through this tutorial with the source code at your side. The tutorial isn't going to cover every line of code but should give you enough to fully understand how it works.
Context highlighted source is also available here:
Game.java
SpriteStore.java
Sprite.java
Entity.java
ShipEntity.java
ShotEntity.java
AlienEntity.java
SystemTimer.java
Disclaimer: This tutorial is provided as is. I don't guarantee that the provided source is perfect or that that it provides best practices.
Currently, Java has some issues with timing. In Java 1.4.2 the simplest way to get hold of the time is to use System.currentTimeMillis(). Infact it was used to time the movement in the first tutorial. However, on some Windows based machines the resolution of the timer (the smallest value you can time) isn't very useful. On most systems (Linux, SunOS, etc) the resolution is at least 1 millisecond. However, on some Windows sytems the resolution is bad enough to mean that timing can be relied on to give a consistant result, which in turn can lead to stuttering in
updates. Lets look at some possible solutions.
One way to deal with the inconsistency of timing on Windows is to average the change in time between frames. Historically this was a very common way to deal with the problem. Using this method the change in time between frames is recorded across the last 5 (or more) frames. Instead of using the actual change in time, the average of the last few frames is taken and used instead.
So the algorithm looks something like this:
While this doesn't give perfect results, it does give "good" results. Its not required on anything other than Windows. So if you do choose to implement this method its nice to add a check on the system property "os.name".
Using the Java 1.5 timer couldn't be simpler:
long currentTime = System.nanoTime()
There you have it! As a side note, there is a high resolution timer "hidden" in Java 1.4.2, however this tutorial doesn't cover it. Why? There is no guarantee the hidden timer will be available in any given JVM and hence there is no point using it except in very specific circumstances.
EDIT: Obviously this tutorial was written before Java 1.5, 1.6 or any future release of Java. Java 1.5 did indeed bring System.nanoTime(), unfortunately as of now (1.6 being the current release) nanoTime() is still bugged and responds in strange ways on windows and any system with multiple processors. The advice given here to consider a native library still holds true - GAGETimer in particular will adapt based on the current version of Java.
Well, that sounds terrible doesn't it? We have to wait for Sun to update the JVM before we can access the latest and greatest platform dependant feature. Actually, no, this is exactly what the Java Native Interface (JNI) is designed to allow. The interface lets us use native libraries from Java to access features of the platform not yet exposed to the JVM. However, the downside is that unless you implement a native library for every platform your software now only works on a particular operating system. Its a trade off that should be taken pretty seriously when
you're looking at writing Java software.
Whats even better, is that there are at least a few free implementations of this timing library already available, so we don't even need to touch the C++. A good implementation that a large number of people use is the GAGE Timer. Its freely available and has a good track record.
If you download the GAGE Timer package, you'll have a dynamic link library (DLL) for Windows and a Jar file containing the interface to the timer. To continue with this tutorial the DLL must be in the directory you are running from (only if you're on Windows of course), and the Jar file must be referenced in your classpath.
package org.newdawn.spaceinvaders;
import com.dnsalias.java.timer.AdvancedTimer;
/**
* A wrapper class that provides timing methods. This class
* provides us with a central location where we can add
* our current timing implementation. Initially, we're going to
* rely on the GAGE timer. (@see http://java.dnsalias.com)
*
* @author Kevin Glass
*/
public class SystemTimer {
/** Our link into the GAGE timer library */
private static AdvancedTimer timer = new AdvancedTimer();
/** The number of "timer ticks" per second */
private static long timerTicksPerSecond;
/** A little initialisation at startup, we're just going to get the GAGE timer going */
static {
timer.start();
timerTicksPerSecond = AdvancedTimer.getTicksPerSecond();
}
/**
* Get the high resolution time in milliseconds
*
* @return The high resolution time in milliseconds
*/
public static long getTime() {
// we get the "timer ticks" from the high resolution timer
// multiply by 1000 so our end result is in milliseconds
// then divide by the number of ticks in a second giving
// us a nice clear time in milliseconds
return (timer.getClockTicks() * 1000) / timerTicksPerSecond;
}
/**
* Sleep for a fixed number of milliseconds.
*
* @param duration The amount of time in milliseconds to sleep for
*/
public static void sleep(long duration) {
timer.sleep((duration * timerTicksPerSecond) / 1000);
}
}
This timer class is based on the use of the GAGE timer. We need to support two main operations, getting the time and sleeping for a set period of time. The gage timer supports both of these operations, however it requires you specify the time in "timer ticks" not in milliseconds. The main job of this class is to map between the timer ticks provided
from the native timer to milliseconds and back.
Simply put, we create an "AdvancedTimer", the timer given to us by GAGE. We find out its resolution and start it off running. The final step is to provide our methods based on using the timer.
// work out how long its been since the last update, this // will be used to calculate how far the entities should // move this loop long delta = SystemTimer.getTime() - lastLoopTime; lastLoopTime = SystemTimer.getTime();
Now, in the last tutorial since the timer wasn't designed to be perfect we didn't worry to much about a few lost milliseconds. This time we can rely on our timer to be millisecond accurate so we're going to try and strictly limit our frame time so we get exactly 100 frames per second (FPS).
To do this we're going to want each cycle round the game loop to take exactly 10 milliseconds. We know at what time the cycle started (lastLoopTime) and we know what time it is now, so with a small amount of maths we can sleep for the right amount of time like this:
// we want each frame to take 10 milliseconds, to do this // we've recorded when we started the frame. We add 10 milliseconds // to this and then factor in the current time to give // us our final value to wait for SystemTimer.sleep(lastLoopTime+10-SystemTimer.getTime());
Note: GAGE Timer actually supports a "sleepUntil()" method that could be used here. However, since the SystemTimer is trying to allow us to change between timing mechanisms we should try to rely on simply sleeping for the right amount of time.
Since we designed our source nicely last time our changes are limited to one class, AlienEntity. Instead of the entity maintaining just a single sprite we'll add a few sprites and flip between them over time, i.e. Animation. Our first step is to add some addition variables to our AlienEntity:
/** The animation frames */ private Sprite[] frames = new Sprite[4]; /** The time since the last frame change took place */ private long lastFrameChange; /** The frame duration in milliseconds, i.e. how long any given frame of animation lasts */ private long frameDuration = 250; /** The current frame of animation being displayed */ private int frameNumber;
The frames array is going to hold our frames of animation. lastFrameChange is going to be a record of the last time we changed animation frame. frameDuration will be the length of time that each frame will be displayed on the screen. Making this small will make the aliens dance more quickly. Finally, frameNumber will be the index of the frame we are currently showing in a frames array. This will be incremented to cycle us through the animation.
Next we're going to need to load up our sprites. We're going to modify the constructor to grab the frames. However, our standard Entity class will already load one sprite for us (the one it used to display). So we need to load two additional ones, like so:
public AlienEntity(Game game,int x,int y) {
super("sprites/alien.gif",x,y);
// setup the animatin frames
frames[0] = sprite;
frames[1] = SpriteStore.get().getSprite("sprites/alien2.gif");
frames[2] = sprite;
frames[3] = SpriteStore.get().getSprite("sprites/alien3.gif");
this.game = game;
dx = -moveSpeed;
}
We've asked the Entity class to load "sprites/alien.gif" for us. Then we need to go off and load up a couple of additional sprites. We put the frame loaded by Entity and our two additional frames in the array in the right place to play the animation. Note that we've modified the constructor slightly to remove the name of the sprite, so
the Game class wll need some minor modifications.
The final step in getting our animation to play is to update the current sprite as time progresses. We already have a handy place in which we can perform this action. Our "move()" method already gets told when time passes, so we can update the animation there.
public void move(long delta) {
// since the move tells us how much time has passed
// by we can use it to drive the animation, however
// its the not the prettiest solution
lastFrameChange += delta;
// if we need to change the frame, update the frame number
// and flip over the sprite in use
if (lastFrameChange > frameDuration) {
// reset our frame change time counter
lastFrameChange = 0;
// update the frame
frameNumber++;
if (frameNumber >= frames.length) {
frameNumber = 0;
}
sprite = frames[frameNumber];
}
...
}
So, as time passes our lastFrameChange counter will get updated. Once its passed our frameDuration limit we reset it. In addition we move to the next frame number. Then we reset the current sprite by setting the "sprite" member in the Entity super class to the current frame of animation. Next time the entity is rendered a different sprite is
drawn and the animation takes place!
If you have any comments or corrections feel free to mail me here
Tutorial and Source written by Kevin Glass
Game sprites provided by Ari Feldman
A large number of people over at the Java Gaming Forums
The result of this tutorial can be seen here. The complete source for the tutorial can be found here. Its intended that you read through this tutorial with the source code at your side. The tutorial isn't going to cover every line of code but should give you enough to fully understand how it works.
Context highlighted source is also available here:
Disclaimer: This tutorial is provided as is. I don't guarantee that the provided source is perfect or that that it provides best practices.
Our last space invaders development was solely based around Java2D. This time round we'd like to support OpenGL. Now we could just rehack the source code to replace the Java2D implementation with an OpenGL version. While in many cases this would be perfectly logical, in this case what'd we'd really like is to be able to compare and contrast the two versions. Even more importantly, in the future we could anticipate using yet another different method of rendering. This is where refactoring comes in.
Refactoring is generally the process of taking a piece of code that put together to a specific job and redesigning it to be more flexible and/or easier to maintain. It happens to most developers at some point, when starting a project the intentions were one thing. Later on, having understood the problem more fully and having realised the mistakes it seems like a good idea to start a fresh piece of code or to rewrite what you have. At this point the best bet is to refactor the code, normally before adding anything else. In this way, often refactoring itself can be a very thankless process since the functionality you have afterwards should be very similar to what you had before. However, as a good Java/OO developer you have to trust that redesigning your source is going to save you time in the long run.
In games its generally desirable to have decided what you're going to use to render at the begining. This means you don't spend alot of time trying to get multiple rendering methods displaying something that is very easy to produce in one of them. For instance, fast alpha blending is difficult to achieve in Java2D, however in OpenGL its very easy. Enforcing that we support both rendering methods can be difficult and apart from anything else a waste of valuable development time. All this being said, in this case we're trying look at the differences in rendering and so thats exactly what we're going to do. We're going introduce a set of interfaces that describe what we need our rendering layer to be able to do for us. Then we'll supply implementations of these interfaces for each of our intended rendering layers.
package org.newdawn.spaceinvaders;
/**
* The window in which the game will be displayed. This interface exposes just
* enough to allow the game logic to interact with, while still maintaining an
* abstraction away from any physical implementation of windowing (i.e. AWT, LWJGL)
*
* @author Kevin Glass
*/
public interface GameWindow {
/**
* Set the title of the game window
*
* @param title The new title for the game window
*/
public void setTitle(String title);
/**
* Set the game display resolution
*
* @param x The new x resolution of the display
* @param y The new y resolution of the display
*/
public void setResolution(int x,int y);
/**
* Start the game window rendering the display
*/
public void startRendering();
/**
* Set the callback that should be notified of the window
* events.
*
* @param callback The callback that should be notified of game
* window events.
*/
public void setGameWindowCallback(GameWindowCallback callback);
/**
* Check if a particular key is pressed
*
* @param keyCode The code associate with the key to check
* @return True if the particular key is pressed
*/
public boolean isKeyPressed(int keyCode);
}
setTitle(), setResolution() and startRendering() are directly related to the window and game loop. We have two extra additions here. First off, just like rendering to the screen we need our game window to provide with a way to check if a given key is pressed. In the last version we used standard KeyListeners to do this for us, however if we want to be truely independant of how our game is rendered we need this to be abstract this as well. In addition we've added setGameWindowCallback(), this is how we're going to hook into the game loop. The class that we set as GameWindowCallback is going to be notified each time the frame is being rendered. This will give us a chance to draw our game in frame. The GameWindowCallback interface looks like this:
package org.newdawn.spaceinvaders;
/**
* An interface describing any class that wishes to be notified
* as the game window renders.
*
* @author Kevin Glass
*/
public interface GameWindowCallback {
/**
* Notification that game should initialise any resources it
* needs to use. This includes loading sprites.
*/
public void initialise();
/**
* Notification that the display is being rendered. The implementor
* should render the scene and update any game logic
*/
public void frameRendering();
/**
* Notification that game window has been closed.
*/
public void windowClosed();
}
As you can see the GameWindowCallback has just a bit more than frame rendering notification in it. We have an initialise method, thats going to be called at startup. Many resources that we load are dependant on the rendering layer being in the right state. For instance, in Java2D when we load our sprites we need to make sure we've changed graphics modes first, so the sprites get loaded in the right format. Finally theres an additional hook to notify when the game rendering window has been closed.
So, our game window implementations will be responsible for creating an actual window, and running a game loop. In this game loop the GameWindow is going to call frameRendering() to let us know that we need to draw our game, but how are we going to draw our game without knowing which rendering layer is in use? To solve this we're going to have to create an interface for our main drawing tool, Sprite. Intead of Sprite being a concrete class that draws a sprite to Java2D we're going to make it an interface that can be implemented by the different rendering layers. Here's what we required from the sprite:
package org.newdawn.spaceinvaders;
/**
* A sprite to be displayed on the screen. Note that a sprite
* contains no state information, i.e. its just the image and
* not the location. This allows us to use a single sprite in
* lots of different places without having to store multiple
* copies of the image.
*
* @author Kevin Glass
*/
public interface Sprite {
/**
* Get the width of the drawn sprite
*
* @return The width in pixels of this sprite
*/
public int getWidth();
/**
* Get the height of the drawn sprite
*
* @return The height in pixels of this sprite
*/
public int getHeight();
/**
* Draw the sprite onto the graphics context provided
*
* @param x The x location at which to draw the sprite
* @param y The y location at which to draw the sprite
*/
public void draw(int x,int y);
}
For those who followed the first two tutorials this interface should look very familiar. Essentially it matches the interface of the original Sprite class in the Java2D implementations. This happens quite often when refactoring code, its normally called "extracting the interface". What we're trying to say here is that any class that represents a sprite that can be drawn to the screen much be able to do the specified things.
Now we've defined what resources we need to play out game, GameWindow and Sprite, and we've defined what these resources must be able to do for us. Next we can look at how we implement these two resources in the different rendering methods.
The only significant change is in the game loop, instead of drawing the sprites within the actual loop we're going to call a method on the GameWindowCallback and rely on which ever class implements that interface to draw our sprites. The game loop looks like this:
private void gameLoop() {
while (gameRunning) {
// Get hold of a graphics context for the accelerated
// surface and blank it out
g = (Graphics2D) strategy.getDrawGraphics();
g.setColor(Color.black);
g.fillRect(0,0,800,600);
if (callback != null) {
callback.frameRendering();
}
// finally, we've completed drawing so clear up the graphics
// and flip the buffer over
g.dispose();
strategy.show();
}
}
We've extracted a portion of the original Game class which was responsible for setting up the window to be rendered in. Next, if a callback has been registered we notify it that a game frame is being rendered and hence it should draw anything to the screen its going to. Finally, another part of the original class that is responsible for swapping over our Java2D buffer strategy.
Everything else in Java2DGameWindow has been extracted from the original Game class with the exception of the keyboard handling. Since we're going to want similar keyboard handling in both OpenGL and Java2D this has been pulled out into a seperate class called "Keyboard". However, this will be covered further on in this tutorial.
The next thing we should look at is Java2DSprite. If you look through the code you'll find its almost identical to the original Sprite class from the Java2D tutorial. However, there is one small, but very important change, here:
public void draw(int x,int y) {
window.getDrawGraphics().drawImage(image,x,y,null);
}
Note that the graphics context is no longer passed into the draw method. When we ask a Sprite to draw itself we do so from a class that does not know what type of rendering is going on. However, since a sprite must be being drawn into a window the sprite can now obtain its graphics context from the window itself (which is parameter to its construction).
Thats pretty much it for refactoring. We've made our original code much more abstract by introducing interfaces for each of the resources. In addition we've changed our Java2D code to match up with our new interfaces. Next lets look at the keyboard handling.
public class Keyboard {
/** The status of the keys on the keyboard */
private static boolean[] keys = new boolean[1024];
/**
* Initialise the central keyboard handler
*/
public static void init() {
Toolkit.getDefaultToolkit().addAWTEventListener(new KeyHandler(),
AWTEvent.KEY_EVENT_MASK);
}
/**
* Initialise the central keyboard handler
*
* @param c The component that we will listen to
*/
public static void init(Component c) {
c.addKeyListener(new KeyHandler());
}
/**
* Check if a specified key is pressed
*
* @param key The code of the key to check (defined in KeyEvent)
* @return True if the key is pressed
*/
public static boolean isPressed(int key) {
return keys[key];
}
/**
* Set the status of the key
*
* @param key The code of the key to set
* @param pressed The new status of the key
*/
public static void setPressed(int key,boolean pressed) {
keys[key] = pressed;
}
We are trying to write a nice reusable class so there will be some extra functionality in the eyboard class`that isn't used directly here. Its just nice to implement these things up front so there ready and waiting when you get round to needing them.
First in our Keyboard class we need a set of flags to indicate whether a given key is pressed and a couple of accessor methods to allow us to check if a key is pressed and indicate that a key is pressed. When we initialise the Keyboard class we can do it two ways. First, and most commonly`we can pass in a component. A key listener will be added to this component and the state recorded. On the other hand it sometimes helps to be able to respond to key presses on a global scale, not localised to a particular component. With this in mind theres a second init method which allows us to add a AWTEventListener directly to the AWT event queue. This will pick up any keyboard events at a global level.
Now on to the all important key listener class. This is a bit specialised so don't be surprised if it looks a bit complicated,
private static class KeyHandler extends KeyAdapter implements AWTEventListener {
/**
* Notification of a key press
*
* @param e The event details
*/
public void keyPressed(KeyEvent e) {
if (e.isConsumed()) {
return;
}
keys[e.getKeyCode()] = true;
}
/**
* Notification of a key release
*
* @param e The event details
*/
public void keyReleased(KeyEvent e) {
if (e.isConsumed()) {
return;
}
KeyEvent nextPress = (KeyEvent) Toolkit.getDefaultToolkit().
getSystemEventQueue().
peekEvent(KeyEvent.KEY_PRESSED);
if ((nextPress == null) ||
(nextPress.getWhen() != e.getWhen()) ||
(nextPress.getKeyCode() != e.getKeyCode())) {
keys[e.getKeyCode()] = false;
}
}
/**
* Notification that an event has occured in the AWT event
* system
*
* @param e The event details
*/
public void eventDispatched(AWTEvent e) {
if (e.getID() == KeyEvent.KEY_PRESSED) {
keyPressed((KeyEvent) e);
}
if (e.getID() == KeyEvent.KEY_RELEASED) {
keyReleased((KeyEvent) e);
}
}
}
The first thing thats complicated about this class is that its actually being two different things. First of add its a key listener (well its a KeyAdapter) and secondly its implementing the AWTEventListener interface.
AWTEventListener allows us to be notified of events directly as they become available to the AWT event queue. While this is immensely useful, its also a bit dirty in that we get passed the events as AWTEvent. In our case we're only interested in key events so we can check the ID of the event and then palm off the event to the method that would normally recieve it. This all happens in eventDispatched().
The other part that makes this key handler difficult to understand is the extra bits of code in the keyReleased() method. It would seem to be as simple as indicating that the key is no longer pressed by setting the appropriate index in the keys array to false. However, theres an issue here where Java isn't quite as platform independant as we'd like it to be. When you hold a key down on a keyboard you get a slight pause then the key begins to repeat across the screen. This is normal everywhere. However, the notification for this in Java changes from platform to platform. On Windows when the key begins to repeat no extra notifications are sent, the key is considered still to be pressed. However, on Linux when a key begins to repeat a notifcation of release and then pressed is sent for each repeat. This means that if the user was to hold down the key then our little space ship would move a bit, then stop, then move a bit, then stop, and so on.
Getting round this is a little more complicated than you'd think. In this case we end up with the following code:
KeyEvent nextPress = (KeyEvent) Toolkit.getDefaultToolkit().
getSystemEventQueue().
peekEvent(KeyEvent.KEY_PRESSED);
if ((nextPress == null) ||
(nextPress.getWhen() != e.getWhen()) ||
(nextPress.getKeyCode() != e.getKeyCode())) {
keys[e.getKeyCode()] = false;
}
When a keyReleased notification is recieved we search the pending AWT events by using peekEvent() for a keyPressed event. If there is a keyPressed event scheduled for the exact same moment the keyReleased event is scheduled then its auto-repeat on linux causing the events. At which point we ignore it. Complicated and annoying, Java has a few things like this that are slowly being sorted out.
Having gone through all that, as long as we've called one of the init methods on our Keyboard class we can now implement our GameWindow's isKeyPressed method by calling keyPressed() here. As we implement other rendering methods that use AWT (JOGL for instance) we can now reuse our class to handle key input.
public Sprite getSprite(String ref) {
if (window == null) {
throw new RuntimeException(
"Attempt to retrieve sprite before game window was created");
}
switch (renderingType) {
case JAVA2D:
{
return Java2DSpriteStore.get().getSprite(
(Java2DGameWindow) window,ref);
}
case OPENGL_JOGL:
{
return new JoglSprite((JoglGameWindow) window,ref);
}
}
throw new RuntimeException("Unknown rendering type: "+renderingType);
}
We ask the ResourceFactory for a sprite given by a specific reference. Depending on the type of rendering we're using the appropriate type of sprite is created (be it Java2DSprite or JoglSprite). However, its passed back as the interface Sprite, this means that caller doesn't need to worry about which type of rendering is being used. Note, we use a JoglSprite here that we're going to go on an implement later in this tutorial.
If you check through the rest of ResourceFactory you'll find its pretty simple after understanding this. The only hook into the type of rendering performed is provided by the rather explcitly named "setRenderingType()". We'll call this from our main game class to set which type of rendering we want to use.
The first thing to note is that since we've extracted all the window management and game loop functionality and added it to the Java2DGameWindow we can remove it all from the Game class. This leaves the game class as a collection of bits of game logic and a piece of code to render the game screen. In addition to removing the window management code we can also strip out the keyboard
code since that is also handled by the rendering layer.
Instead of creating the window directly we're going to ask the ResourceFactory class for it. We'll register ourselfs as the GameWindowCallback so we'll get notified of rendering. Finally, instead of starting out own game loop we simply ask the GameWindow to start rendering which is effectively our old game loop.
public Game(int renderingType) {
// create a window based on a chosen rendering method
ResourceFactory.get().setRenderingType(renderingType);
window = ResourceFactory.get().getGameWindow();
window.setResolution(800,600);
window.setGameWindowCallback(this);
window.setTitle(windowTitle);
}
public void startGame() {
window.startRendering();
}
Now, we have a very pure and simple class. The final step in locking together the rendering layer and game logic is to modify the Game class to implement the GameWindowCallback interface. This means moving all resource loading into the initialise() method (note that this includes entity creation),
and modifying the gameLoop() method to be the frameRendering() method. We no longer need to manage the game loop since the GameWindow in use is going to do that for us.
Thats it, we're done. At this stage we should be able to play our Java2D version of the game as before. There are some minor changes in addition to those noted above which complete the picture which are to do with swapping over from explictly creating our resource to asking the ResourceFactory for them. Our next and probably more interesting step is going to be to provide an OpenGL rendering layer. We won't need to touch the game logic again so from here on should be plain sailing.
OpenGL does of course has many advantages. On most platforms you'll get hardware acceleration, there are many special effects that are easily achievable with OpenGL.
The only downside of the LWJGL is that you need to buy into all of the ideas behind the library or none of them. Assuming you agree with everything the library has been written around then this isn't a problem.
With luck JOGL will be making it into the JDK at some later date. As/when this happens JOGL can be considered part of pure Java. With this in mind I'll use JOGL as our native layer for this tutorial.
initialise() - Called by JOGL to request that you initialise any resource you require. This maps directly to our GameWindow's initialise() method. The GLDrawable passed in can be used to access GL during this method.
display() - Called by JOGL to request that you render the scene. This effectively our hook into JOGL for our game loop. The GLDrawable passed in can be used render to the screen while in this method.
reshape() - Called to indicate that the window has been resized or moved. Most of the time we don't need to do anything here apart from adapting for resolution.
displayChanged() - Called to indicate that the graphics mode has been changed. For our purposes this will never happen.
Here's how these things look in the our JoglGameWindow. We'll look at the rest of the code piece by piece later on.
public class JoglGameWindow implements GLEventListener,GameWindow {
/** The frame containing the JOGL display */
private Frame frame;
/** The callback which should be notified of window events */
private GameWindowCallback callback;
/** The width of the game display area */
private int width;
/** The height of the game display area */
private int height;
/** The canvas which gives us access to OpenGL */
private GLCanvas canvas;
/** The OpenGL content, we use this to access all the OpenGL commands */
private GL gl;
/** The loader responsible for converting images into OpenGL textures */
private TextureLoader textureLoader;
/**
* Create a new game window that will use OpenGL to
* render our game.
*/
public JoglGameWindow() {
frame = new Frame();
}
/**
* Retrieve access to the texture loader that converts images
* into OpenGL textures. Note, this has been made package level
* since only other parts of the JOGL implementations need to access
* it.
*
* @return The texture loader that can be used to load images into
* OpenGL textures.
*/
TextureLoader getTextureLoader() {
return textureLoader;
}
/**
* Get access to the GL context that can be used in JOGL to
* call OpenGL commands.
*
* @return The GL context which can be used for this window
*/
GL getGL() {
return gl;
}
/**
* Set the title of this window.
*
* @param title The title to set on this window
*/
public void setTitle(String title) {
frame.setTitle(title);
}
/**
* Set the resolution of the game display area.
*
* @param x The width of the game display area
* @param y The height of the game display area
*/
public void setResolution(int x, int y) {
width = x;
height = y;
}
/**
* Start the rendering process. This method will cause the
* display to redraw as fast as possible.
*/
public void startRendering() {
canvas = GLDrawableFactory.getFactory().createGLCanvas(new GLCapabilities());
canvas.addGLEventListener(this);
canvas.setNoAutoRedrawMode(true);
canvas.setFocusable(true);
Keyboard.init(canvas);
Animator animator = new Animator(canvas);
// Setup the canvas inside the main window
frame.setLayout(new BorderLayout());
frame.add(canvas);
frame.setResizable(false);
canvas.setSize(width, height);
frame.pack();
frame.show();
// add a listener to respond to the user closing the window. If they
// do we'd like to exit the game
frame.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
if (callback != null) {
callback.windowClosed();
} else {
System.exit(0);
}
}
});
// start a animating thread (provided by JOGL) to actively update
// the canvas
animator.start();
}
/**
* Register a callback that will be notified of game window
* events.
*
* @param callback The callback that should be notified of game
* window events.
*/
public void setGameWindowCallback(GameWindowCallback callback) {
this.callback = callback;
}
/**
* Check if a particular key is current pressed.
*
* @param keyCode The code associated with the key to check
* @return True if the specified key is pressed
*/
public boolean isKeyPressed(int keyCode) {
return Keyboard.isPressed(keyCode);
}
/**
* Called by the JOGL rendering process at initialisation. This method
* is responsible for setting up the GL context.
*
* @param drawable The GL context which is being initialised
*/
public void init(GLDrawable drawable) {
// get hold of the GL content
gl = drawable.getGL();
// enable textures since we're going to use these for our sprites
gl.glEnable(GL.GL_TEXTURE_2D);
// set the background colour of the display to black
gl.glClearColor(0, 0, 0, 0);
// set the area being rendered
gl.glViewport(0, 0, width, height);
// disable the OpenGL depth test since we're rendering 2D graphics
gl.glDisable(GL.GL_DEPTH_TEST);
textureLoader = new TextureLoader(drawable.getGL());
if (callback != null) {
callback.initialise();
}
}
/**
* Called by the JOGL rendering process to display a frame. In this
* case its responsible for blanking the display and then notifing
* any registered callback that the screen requires rendering.
*
* @param drawable The GL context component being drawn
*/
public void display(GLDrawable drawable) {
// get hold of the GL content
gl = canvas.getGL();
// clear the screen and setup for rendering
gl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT);
gl.glMatrixMode(GL.GL_MODELVIEW);
gl.glLoadIdentity();
// if a callback has been registered notify it that the
// screen is being rendered
if (callback != null) {
callback.frameRendering();
}
// flush the graphics commands to the card
gl.glFlush();
}
/**
* Called by the JOGL rendering process if and when the display is
* resized.
*
* @param drawable The GL content component being resized
* @param x The new x location of the component
* @param y The new y location of the component
* @param width The width of the component
* @param height The height of the component
*/
public void reshape(GLDrawable drawable, int x, int y, int width, int height) {
gl = canvas.getGL();
// at reshape we're going to tell OPENGL that we'd like to
// treat the screen on a pixel by pixel basis by telling
// it to use Orthographic projection.
gl.glMatrixMode(GL.GL_PROJECTION);
gl.glLoadIdentity();
gl.glOrtho(0, width, height, 0, -1, 1);
}
/**
* Called by the JOGL rendering process if/when the display mode
* is changed.
*
* @param drawable The GL context which has changed
* @param modeChanged True if the display mode has changed
* @param deviceChanged True if the device in use has changed
*/
public void displayChanged(GLDrawable drawable,
boolean modeChanged,
boolean deviceChanged) {
// we're not going to do anything here,
// we could react to the display
// mode changing but for the tutorial there's not much point.
}
}
As you can see, using GL from inside the initialise() and display() methods is fairly simple. The most complicated part of the game window is the initialisation of the main window, this all takes place in startRendering(). First off lets look at how we initialise JOGL.
canvas = GLDrawableFactory.getFactory().createGLCanvas(new GLCapabilities()); canvas.addGLEventListener(this); canvas.setNoAutoRedrawMode(true); canvas.setFocusable(true);
We first create a canvas based on a set of GLCapabilities. These capabilities can define exactly what abilities the OpenGL context we create must have. For our purposes we'll just use the default set which suits us fine for some simple 2D graphics. Next, we set ourselfs up as the GLEventListener so we'll get notified when intiailisation and rendering happen. Finally, we configure the canvas to be the focused component and to require active redrawing.
Just like in the Java2DGameWindow we create a Frame and add the created canvas to it. The last thing we need to do is start some sort of game loop. Since we want our GLCanvas to be actively redrawn we can use a utility class provided from JOGL, using these two lines:
Animator animator = new Animator(canvas); ... animator.start();
The animator will cause the GLCanvas to be initialised and to be actively redrawn as fast as possible, which is essentially what we do in Java2DGameWindow except we don't have a useful utility class to do it for us.
And thats it! Setting up an OpenGL rendering surface is very simple using JOGL. However, now we need to look at sprites.
public JoglSprite(JoglGameWindow window,String ref) {
try {
this.window = window;
texture = window.getTextureLoader().getTexture(ref);
...
The texture loader caches image references and so we don't have to worry about it loading the same image more than once. The texture object returned stores everything we need to use the texture.
The final change is to actually draw the sprite to the screen using OpenGL, that looks like this:
public void draw(int x, int y) {
// get hold of the GL content from the window in which we're drawning
GL gl = window.getGL();
// store the current model matrix
gl.glPushMatrix();
// bind to the appropriate texture for this sprite
texture.bind(gl);
// translate to the right location and prepare to draw
gl.glTranslatef(x, y, 0);
gl.glColor3f(1,1,1);
// draw a quad textured to match the sprite
gl.glBegin(GL.GL_QUADS);
{
gl.glTexCoord2f(0, 0);
gl.glVertex2f(0, 0);
gl.glTexCoord2f(0, texture.getHeight());
gl.glVertex2f(0, height);
gl.glTexCoord2f(texture.getWidth(), texture.getHeight());
gl.glVertex2f(width,height);
gl.glTexCoord2f(texture.getWidth(), 0);
gl.glVertex2f(width,0);
}
gl.glEnd();
// restore the model view matrix to prevent contamination
gl.glPopMatrix();
}
First we retrieve access to the GL context from the window we're rendering into. Just like in the Java2D version we do this inside the class to prevent the caller having to worry about where the sprite is being rendered.
Next we indicate to OpenGL which texture we're using for this sprite. The texture object returned from the texture loader has a simple bind() method to allow us to do this. Once we've told GL which texture to use we simply draw a quad where we want the sprite to appear. For each vertex on the quad we specify a texture coordinate, normally called texture mapping. Note, the use of texture.getWidth() and texture.getHeight(), this is to adapt the texture mapping for the size of texture that has been allocated. When textures are created they must be certain sizes and only a proprotion of them may need to be mapped across the quad. The texture object returned from the texture contains information decribing what proporption of the texture has been used. This is used to map the texture exactly across the quad.
So, to draw a sprite in OpenGL we load a texture, draw a quad and map the texture across it. There are many other ways of drawing similar sprites in OpenGL (glBitmap for instance). There are also ways to optimize these calls using GL lists and vertex buffers. However, for simple 2D games most this isn't really required.
Hopefully this tutorial has been of some help. Personally, I've only used JOGL, hence the tutorial being oriented towards it, however I've heard LWJGL is a great API and I'd really recommend checking it out.
Another interesting point is that in Java 1.5 the Java 2D is going to support OpenGL, that is it will be possible to render the Java2D graphics using OpenGL. Hopefully this is going to make using OpenGL for 2D graphics in Java redundant.
If you have any comments or corrections feel free to mail me here
Game sprites provided by Ari Feldman
A large number of people over at the Java Gaming Forums
The past tutorials, apart from designing and writing a simple space invaders game, have been building an infrastructure for comparing the use of rendering techniques. So, hopefully this is going to pay off now when we attempt to start using LWJGL. So in this tutorial we're going to cover:
This is going to a relatively short tutorial for several reasons. LWJGL is very simple to work with and has some strong similarities to JOGL and hence the last tutorial.
The final game can be see here. The complete source for the tutorial can be found here. Its intended that you read through this tutorial with the source code at your side. The tutorial isn't going to cover every line of code but should give you enough to fully understand how it works.
Context hightlighted source is also available here:
This seems like a trivial point, especially when being illustrated by such a simplistic demo as this spaceinvaders game. However, LWJGL provides not just a graphics binding but a binding to OpenAL (an open standards sound system). In addition, LWJGL gives your access to input devices. This means your spaceinvaders game could be controlled from a gamepad and the aliens could squeltch as they die, all from one library. Worth thinking about.
Adding a new rendering implementation for our SpaceInvaders game should be pretty simple (especially as in this case I didn't have to write the code, thanks Matzon). We're going to add 4 classes then plug then into our architecture.
As with the other rendering layers the first thing we need to do is create a window and notify the infrastructure. The initial part of understanding LWJGL is to realise there is only a single window, the one in which the game runs. So, to create and configure our game window we need to configure a display mode for that window like this:
/**
* Sets the display mode for fullscreen mode
*/
private boolean setDisplayMode() {
try {
// get modes
DisplayMode[] dm = org.lwjgl.util.Display.getAvailableDisplayModes(width,
height, -1, -1, -1, -1, 60, 60);
org.lwjgl.util.Display.setDisplayMode(dm, new String[] {
"width=" + width,
"height=" + height,
"freq=" + 60,
"bpp=" + org.lwjgl.opengl.Display.getDisplayMode().getBitsPerPixel()
});
return true;
} catch (Exception e) {
e.printStackTrace();
System.out.println("Unable to enter fullscreen, continuing in windowed mode");
}
return false;
}
Since there is only one window, the static call on the "Display" class makes sense. The next thing to accept is that there is only one OpenGL context at any given time. Hence we can also access this in a purely static way. These sort of assumptions are one of the things that make LWJGL simple to use.
As you can see from the following code, OpenGL calls are simply prefixed with the class "GL11":
public void startRendering() {
try {
setDisplayMode();
Display.create();
// grab the mouse, dont want that hideous cursor when we're playing!
Mouse.setGrabbed(true);
// enable textures since we're going to use these for our sprites
GL11.glEnable(GL11.GL_TEXTURE_2D);
// disable the OpenGL depth test since we're rendering 2D graphics
GL11.glDisable(GL11.GL_DEPTH_TEST);
GL11.glMatrixMode(GL11.GL_PROJECTION);
GL11.glLoadIdentity();
GL11.glOrtho(0, width, height, 0, -1, 1);
textureLoader = new TextureLoader();
if(callback != null) {
callback.initialise();
}
} catch (LWJGLException le) {
callback.windowClosed();
}
gameLoop();
}
So, we create our window, initialise OpenGL by setting our options and using glOrtho() to configure our view (for more on this see SpaceInvaders 103). Next we create a texture loader, which we will use to load our sprite images. Finally we notify the infrastructure that the initialisation has been completed.
Also note the line "Mouse.setGrabbed(true);". As we'll see later, the mouse and keyboard are also monitored from static contexts. This line simple says that the mouse cursor shouldn't be displayed in the window.
private void gameLoop() {
while (gameRunning) {
// clear screen
GL11.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT);
GL11.glMatrixMode(GL11.GL_MODELVIEW);
GL11.glLoadIdentity();
// let subsystem paint
if (callback != null) {
callback.frameRendering();
}
// update window contents
Display.update();
if(Display.isCloseRequested() || Keyboard.isKeyDown(Keyboard.KEY_ESCAPE)) {
gameRunning = false;
Display.destroy();
callback.windowClosed();
}
}
}
The final addition here is to check our exit cases. If the user has closed the window (Window.isCloseRequested()) or if the escape key has been pressed (Keyboard.isKeyDown(Keyboard.KEY_SCAPE)) then we need to notify the infrastructure and destroy the game window. In LWJGL we accomplish this by calling Window.destroy().
public boolean isKeyPressed(int keyCode) {
// apparently, someone at decided not to use standard
// keycode, so we have to map them over:
switch(keyCode) {
case KeyEvent.VK_SPACE:
keyCode = Keyboard.KEY_SPACE;
break;
case KeyEvent.VK_LEFT:
keyCode = Keyboard.KEY_LEFT;
break;
case KeyEvent.VK_RIGHT:
keyCode = Keyboard.KEY_RIGHT;
break;
}
return Keyboard.isKeyDown(keyCode);
}
The switch() here is a little confusing, why do we need to map VK_SPACE to KEY_SPACE and so on? LWJGL has stuck to the original key codes provided by "most" operating systems and since its a games oriented API this makes perfect sense. Java's AWT however uses its own set of key codes, which means we need a mapping between the two sets. As you can see, for the purposes of SpaceInvaders this is very simple.
public void draw(int x, int y) {
// store the current model matrix
GL11.glPushMatrix();
// bind to the appropriate texture for this sprite
texture.bind();
// translate to the right location and prepare to draw
GL11.glTranslatef(x, y, 0);
GL11.glColor3f(1,1,1);
// draw a quad textured to match the sprite
GL11.glBegin(GL11.GL_QUADS);
{
GL11.glTexCoord2f(0, 0);
GL11.glVertex2f(0, 0);
GL11.glTexCoord2f(0, texture.getHeight());
GL11.glVertex2f(0, height);
GL11.glTexCoord2f(texture.getWidth(), texture.getHeight());
GL11.glVertex2f(width,height);
GL11.glTexCoord2f(texture.getWidth(), 0);
GL11.glVertex2f(width,0);
}
GL11.glEnd();
// restore the model view matrix to prevent contamination
GL11.glPopMatrix();
}
Note that the only significant change is the use of our GL context. In LWJGL instead of have a GL class implementation, we have static access to the GL11 class which provides us our OpenGL access.
public GameWindow getGameWindow() {
// if we've yet to create the game window, create the appropriate one
// now
if (window == null) {
switch (renderingType) {
case JAVA2D:
{
window = new Java2DGameWindow();
break;
}
case OPENGL_JOGL:
{
window = new JoglGameWindow();
break;
}
case OPENGL_LWJGL:
{
window = new LWJGLGameWindow();
break;
}
}
}
return window;
}
All we had to do was add a new leg to the switch will return the appropriate game window for LWJGL. Finally, we need to make a similar change for sprites. Here we're going to create an LWJGLSprite if we're using LWJGL to render:
public Sprite getSprite(String ref) {
if (window == null) {
throw new RuntimeException("Attempt to retrieve sprite before game window was created");
}
switch (renderingType) {
case JAVA2D:
{
return Java2DSpriteStore.get().getSprite((Java2DGameWindow) window,ref);
}
case OPENGL_JOGL:
{
return new JoglSprite((JoglGameWindow) window,ref);
}
case OPENGL_LWJGL:
{
return new LWJGLSprite((LWJGLGameWindow) window,ref);
}
}
throw new RuntimeException("Unknown rendering type: "+renderingType);
}
And thats it! As noted this is relatively short tutorial. This is partly because our existing infrastruture was designed specifically to support showing the changes in rendering layers. However, hopefully this tutorial has also shown has simple it is to use LWJGL to develop 2D games.
Full screen mode is actually more normal for games development and hence for LWJGL. A simple change to the create() call should change the game into a fullscreen application. However, many systems don't support this functionality fully. Its worth experimenting with this addition with a series of different systems.
A large number of people over at the Java Gaming Forums

This tutorial can be discussed here
Disclaimer: This tutorial is provided as is. I don't guarantee that the provided source is perfect or that that it provides best practices.
Note that this tutorial isn't intended to be guide to writing each and every line of the code. Its designed as a walk through the code for the game which should highlight the bits that are interesting for general games development.
Disclaimer: This tutorial is provided as is. I don't guarantee that the provided source is perfect or that that it provides best practices.
Windows Natives
Mac OS X Natives
Linux x86 Natives
We're also going to utilise the texture loaded provided by the Space Invaders tutorials which can be found here:
TextureLoader.java
Texture.java
The final playable version of the game can be found here: Asteroids Tutorial
While reading the tutorial it makes sense to have the source code open in another window or IDE. In each part the tutorial source applicable will be linked - note however that this will be the complete source and not the half completed version in line with the current stage of the tutorial.
We're going to build a simple framework to allow us to maintain the LWJGL window/display creation code seperately from the rest of the game. This will allow us greater flexibility to extend the game later by reducing the dependcies between the setup code and the game code. Let first look at getting an LWJGL window on the screen...
Although this tutorial is based on using LWJGL most of the code would be an easy port to any other OpenGL binding (e.g. JOGL). Lets take a look at the GameWindow class. This class trys to contain everything for setting up, controlling and managing the LWJGL specific bits of code.
The entry point to the game (main()) is here and simply constructs a GameWindow. If we were going to think about more than just a simple game it might make sense to move this to its own class and add some bootstrap code. However, since we're just writing asteroids its fine here.
The GameWindow class has 3 main functions. First, the constructor is responsible for initialising the LWJGL display and start the whole game off. Here it is:
public GameWindow() { try { // find out what the current bits per pixel of the desktop is int currentBpp = Display.getDisplayMode().getBitsPerPixel(); // find a display mode at 800x600 DisplayMode mode = findDisplayMode(800, 600, currentBpp); // if can't find a mode, notify the user the give up if (mode == null) { Sys.alert("Error", "800x600x"+currentBpp+" display mode unavailable"); return; } // configure and create the LWJGL display Display.setTitle("Asteroids Tutorial"); Display.setDisplayMode(mode); Display.setFullscreen(false); Display.create(); // initialise the game states init(); } catch (LWJGLException e) { e.printStackTrace(); Sys.alert("Error", "Failed: "+e.getMessage()); } } public void startGame() { // enter the game loop gameLoop(); }
So, the first thing we do is try and find a display mode. We're aiming for a windowed version at 800x600. We attempt to get a bit-per-pixel based on what the desktop is currently running at since this is mostly like to work. If something goes wrong we use the Sys.alert() LWJGL function to display a message to the user.
Next, we set a few details up and create the Display. At this point we should get a window on the screen. Great! Finally, we call init() - to initialise the game states (more about these in a moment) and gameLoop() - the method that runs the whole game. gameLoop() doesn't return so we're done here.
The init() method is responsible for creating the game state objects - these objects will conform to the GameState interface which is how the GameWindow will view the rest of game. The GameState interface is intended to decouple the LWJGL window logic from the actual game code. This hopefully allows us to add extra bits of game without having too much effect on the LWJGL code (which in turn helps up to maintain either side and to port to different rendering technologies). Lets take a look at the init() method:
public void init() { // initialise our sound loader to determine if we can // play sounds on this system SoundLoader.get().init(); // run through some based OpenGL capability settings. Textures // enabled, back face culling enabled, depth testing is on, GL11.glEnable(GL11.GL_TEXTURE_2D); GL11.glEnable(GL11.GL_CULL_FACE); GL11.glEnable(GL11.GL_DEPTH_TEST); GL11.glDepthFunc(GL11.GL_LEQUAL); GL11.glShadeModel(GL11.GL_SMOOTH); // define the properties for the perspective of the scene GL11.glMatrixMode(GL11.GL_PROJECTION); GL11.glLoadIdentity(); GLU.gluPerspective(45.0f, ((float) 800) / ((float) 600), 0.1f, 100.0f); GL11.glMatrixMode(GL11.GL_MODELVIEW); GL11.glHint(GL11.GL_PERSPECTIVE_CORRECTION_HINT, GL11.GL_NICEST); // add the two game states that build up our game, the menu // state allows starting of the game. The ingame state rendered // the asteroids and the player addState(new MenuState()); addState(new InGameState()); try { // initialse all the game states we've just created. This allows // them to load any resources they require Iterator states = gameStates.values().iterator(); // loop through all the states that have been registered // causing them to initialise while (states.hasNext()) { GameState state = (GameState) states.next(); state.init(this); } } catch (IOException e) { // if anything goes wrong, show an error message and then exit. // This is a bit abrupt but for the sake of this tutorial its // enough. Sys.alert("Error", "Unable to initialise state: " + e.getMessage()); System.exit(0); } }
The first thing we do here is ask the sound system to initialise. What this is actually for will be convered in detail in 4 - suffice it to say it cause the OpenAL interface to be created. Next we initialise OpenGL configuration by enabling a few bits of pieces:
GL_TEXTURE_2D - We're going to texture objects in the game
GL_CULL_FACE - We're going to speed up rendering by culling faces that point away from the view.
GL_DEPTH_TEST - Depth testing is turned on to prevent things in the background being drawn over things in the foreground.
Next we set up the perspective mode. When we're working in 3D its important to describe to OpenGL how we want the distance from the viewer to effect the size things are drawn on the screen. This is whats called the project matrix (GL_PROJECTION). Here we've used the GL utility method gluPerspective to specify the perspective based on the size of the screen (800x600) and front and back planes (the distances at which things will stop being displayed - i.e. disappear into the distance).
Next we go onto create and add the game states that will control the game play. Having added them we loop through the added states and cause them to initialise - by calling the init() method that all game states have to implement due to their interface. This might seem a bit wierd given that we're the ones that just added the states and we could have initialised them at any point. The intention here is to eventually allow game states to be added externally to the GameWindow, so the game could be extended further. Ok, so why do we call init() on each state so much later than we could? The states could want to initialise textures, or display lists, or any other OpenGL resource. These can only be created once LWJGL has been initialised - so we wait until the Display has been created then go on to initialise the states. Of course, once we've done it this way once and put it in a nice maintainable class we can go on and forget about that sort of detail :)
Ok.. so now our window is up and game states are initialise.. onto the game logic..
The game loop lets the game state get on with the logic while maintaining updating of the LWJGL display. It looks like this:
public void gameLoop() { boolean gameRunning = true; long lastLoop = getTime(); currentState.enter(this); // while the game is running we loop round updating and rendering // the current game state while (gameRunning) { // calculate how long it was since we last came round this loop // and hold on to it so we can let the updating/rendering routine // know how much time to update by int delta = (int) (getTime() - lastLoop); lastLoop = getTime(); // clear the screen and the buffer used to maintain the appearance // of depth in the 3D world (the depth buffer) GL11.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT); // cause the game state that we're currently running to update // based on the amount of time passed int remainder = delta % 10; int step = delta / 10; for (int i=0;i<step;i++) { currentState.update(this, 10); } if (remainder != 0) { currentState.update(this, remainder); } // cause the game state that we're currently running to be // render currentState.render(this,delta); // finally tell the display to cause an update. We've now // rendered out scene we just want to get it on the screen // As a side effect LWJGL re-checks the keyboard, mouse and // controllers for us at this point Display.update(); // if the user has requested that the window be closed, either // pressing CTRL-F4 on windows, or clicking the close button // on the window - then we want to stop the game if (Display.isCloseRequested()) { gameRunning = false; System.exit(0); } } }
So first, we tell the game state we're currently in that we've entered it. This gives game states a chance to do things as they become active - maybe changing the music or displaying an effect.
Next we go into a loop, this is the one thats going to keep the game running. Each loop we work out how much time has passed since we last rendered (often referred to as delta - from the term "change"). We use this time gap to work out how much to update the game on this loop. After this we clear the screen to prepare to render.
Now this next bit is interesting:
// cause the game state that we're currently running to update // based on the amount of time passed int remainder = delta % 10; int step = delta / 10; for (int i=0;i<step;i++) { currentState.update(this, 10); } if (remainder != 0) { currentState.update(this, remainder); }
The method update() on GameState allows the state to update its elements. Maybe its got to move space ships around the screen or progress an animation. So, here we allow the game states to progress based on the amount of time passed since last render. However, we don't want to let the game progress in big jumps (since this might allow us to miss collisions or jump through solid objects) so instead we update the game state in increments of 10 milliseconds. Since the game state adapts based on the amount of time thats passed this has no effect on them apart from making the game logic far more accurate.
Ok, so we've allowed the game states to run whatever logic they want to. Next, we ask the current game state to render itself giving it a reference to ourself so it can access some utility functions (see next section). Once the state has rendered we get LWJGL to update the display causing the rendering to be shown to the player.
Finally in the game loop we check to see if the user has tried to close the window in anyway. If they have we honour this by exiting the game.
Well, thats it for LWJGL display handling. Next lets look at the few LWJGL utilities available from the GameWindow class.
Theres a few things that are display or library related that it'd be nicer to have in the LWJGL specific bit of the code. For this tutorial we've provided methods for the basics, but you could take the whole thing a step further by abstracting the actual rendering opertions so they were independent on the rendering library in use. Of course this would be an awful lot of work. An important point of building any framework it picking the level at which your aiming to provide your tools.
This time we've provided a simple method to get the current time based on the high resolution in the LWJGL library:
private long getTime() { return (Sys.getTime() * 1000) / Sys.getTimerResolution(); }
This just gets us the current time in milliseconds. Its useful for moving elements based on time rather than frame rate (see the Space Invaders tutorials for details).
The other useful utility we're going to expose from GameWindow is orthographic projection mangement. As mentioned above the projection matrix describes how the distance an object is from the viewer will effect its size and position. This gives us the feeling of perspective. However, when we want to draw things in pixel coordinates we don't want this effect. Say we want to draw a line of text on the screen, we want it to appear as though its overlayed over the 3D game world. How do we do this?
If you look back at the space invaders tutorials this is exactly what we were doing. Its called an "orthographic projection matrix". This means that the distance an object is away from the view has no effect on its screen position - which fits nicely with drawing things on the screen. There are a few other details but lets look at the code first..
public void enterOrtho() { // store the current state of the renderer GL11.glPushAttrib(GL11.GL_DEPTH_BUFFER_BIT | GL11.GL_ENABLE_BIT); GL11.glPushMatrix(); GL11.glLoadIdentity(); GL11.glMatrixMode(GL11.GL_PROJECTION); GL11.glPushMatrix(); // now enter orthographic projection GL11.glLoadIdentity(); GL11.glOrtho(0, 800, 600, 0, -1, 1); GL11.glDisable(GL11.GL_DEPTH_TEST); GL11.glDisable(GL11.GL_LIGHTING); } public void leaveOrtho()