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() { // restore the state of the renderer GL11.glPopMatrix(); GL11.glMatrixMode(GL11.GL_MODELVIEW); GL11.glPopMatrix(); GL11.glPopAttrib(); }
What we've got here is a way to start using an orthographic projection matrix (enterOrtho()) and a way to stop using it (leaveOrtho()). Lets look at the second section of enterOrtho() first - just like in space invaders we use the glOrtho() method to define a matrix that will allow us to draw to the screen as though its a normal 2D display. A couple of other things here. First we want our ortho mode graphics to be displayed "above" everything else - like an overlay. To achieve this we disable the depth testing (GL_DEPTH_TEST) which means that elements are drawn to the screen without considering what might be infront of them. Secondly, we don't want the overlay graphics to be effected by lighting so we disable this too (GL_LIGHTING).
Right, so thats how we get into orthographic projection mode but whats all the pushing and popping about? When we look at the rest of the game we'll be manipulating the view matrix so that we can view different parts of the scene. Now, there is only *one* view matrix and setting the orthographic view is going to overwrite that matrix - but we don't want to lose the any setup we might have done for the 3D view - so, how are we going to save it? This is when the matrix stack comes in. Imagine it as a stack of paper where each piece of paper has one matrix written on it. When we "glPushMatrix" we're saving the current matrix onto the stack. When we "glPopMatrix" we're pulling the top matrix off and putting it into the current matrix. We can do the same thing with rendering attributes, see "glPushAttrib" and "glPopAttrib".
So, now we get pushing and popping, what are we actually doing? We save the current matrix and attributes by pushing them onto the stack. Next we setup orthographic projection. When we want to leave othrographic projection mode we simply pop everything back off the stack - simplicity itself!
Now we have looking at everything GameWindow provides its probably worth just having a quick look at the GameState interface and the first implementation of one the InGameState
Lets take a look at the GameState interface that allows the GameWindow to communicate in an abstract way with the rest of the game source.
public interface GameState { public String getName(); public void init(GameWindow window) throws IOException; public void render(GameWindow window, int delta); public void update(GameWindow window, int delta); public void enter(GameWindow window); public void leave(GameWindow window); }
So, the state is responsible for providing its own name. This gives us a way of identifing the states when we want to move between them (say from a menu state into a playing state). GameState's also have a change to render and update themselfs every frame. This allows them to draw the in game graphics and update the related data models.
Finally GameStates are notified when they are about to be activated and when they are about to be deactivated using the enter() and leave() methods repsectively.
We've now got a description of the elements that are going to build up the game logic. Lets take a look at an implementation of this interface used for the in game play.
This state is going to be responsible for rendering the in game models and update the game logic for flying around, shooting and asteroids exploding. First, take a look at init()
public void init(GameWindow window) throws IOException { defineLight(); TextureLoader loader = new TextureLoader(); background = loader.getTexture("res/bg.jpg"); shotTexture = loader.getTexture("res/shot.png"); shipTexture = loader.getTexture("res/ship.jpg"); shipModel = ObjLoader.loadObj("res/ship.obj"); rockTexture = loader.getTexture("res/rock.jpg"); rockModel = ObjLoader.loadObj("res/rock.obj"); Texture fontTexture = loader.getTexture("res/font.png"); font = new BitmapFont(fontTexture, 32, 32); shoot = SoundLoader.get().getOgg("res/hit.ogg"); split = SoundLoader.get().getOgg("res/bush.ogg"); }
We'll talk about what each of the specific details are in the init() method in later part. However, as mentioned above, note how anything that requires a GL context to be present (textures, models etc) when loading is loaded in init(). Here we load a few textures, a couple of models (one of the ship, one for the rocks) and a couple of sounds. We'll cover how things things work later.
Next lets look at the render() implementation:
public void render(GameWindow window, int delta) { // reset the view transformation matrix back to the empty // state. GL11.glLoadIdentity(); material.put(1).put(1).put(1).put(1); material.flip(); GL11.glMaterial(GL11.GL_FRONT, GL11.GL_DIFFUSE, material); GL11.glMaterial(GL11.GL_BACK, GL11.GL_DIFFUSE, material); // draw our background image GL11.glDisable(GL11.GL_LIGHTING); drawBackground(window); // position the view a way back from the models so we // can see them GL11.glTranslatef(0,0,-50); // loop through all entities in the game rendering them for (int i=0;i<entities.size();i++) { Entity entity = (Entity) entities.get(i); entity.render(); } drawGUI(window); }
As you can see the pieces of code get to be quite short and to the point. Here for instance, we apply a material so everything we render will be lit. Next we draw the background, cycle through all the entities getting them to render their models. Finally we draw the GUI over the top of the game view.
In a similar way the update method is also quite simple. The source code is split out into simple blocks that we'll cover in each part of this tutorial. However, the central game state just act as the glue to pull everything together.
There is final point we should look as is the good example of using enter() method from the state interface. This lets us reset all the game statistics when the state is entered, which effectively lets us start a new game when we enter the state:
public void enter(GameWindow window) { entities.clear(); player = new Player(shipTexture, shipModel, shotTexture); entities.add(player); life = 4; score = 0; level = 5; gameOver = false; spawnRocks(level); }
We clear out all the game entities, create a new player, reset the stats and spawns some more rocks to blast.
This part of the tutorial has hopefully explained the simple framework that we're going to use in the later parts to build a simple game.
In the last part we covered a bit about the basis for the game. This section is going to cover the data model thats going to run the game. So, its asteroids right? What game entities are we going to be thinking about?
Asteroids - the lumps of rock floating around the world. They have to be different sizes and we have to be able to detect when they get hit by anything (including themselfs)
Player - The player's ship itself. The player has to be able to control this and like asteroids we must be able to detect when the player hits anything. The player must also be able to fire shots.
Shots - The lazer blasts produced by the player. Again, we must be able to detect when these hit other entities. They must also only last for a short time.
Now we know what our data is we can start thinking about a quick bit of design. We've got a bunch of "things" in the game world, normally referred to as game entities. So, like with space invaders, we end up with an interface called Entity which describes what we expect of all entities and allows us to treat them all the same. We could also note that there are some common bits of functionality (collision, movement) so lets add an AbstractEntity abstract class to hold this. Finally, we end up with three implements, one for each type of entity, here they are:
This is what in the Entity interface this time:
public interface Entity { public void update(EntityManager manager, int delta); public void render(); public float getSize(); public float getX(); public float getY(); public void collide(EntityManager manager, Entity other); public boolean collides(Entity other); }
The first two methods are related to the GameState interface we looked at in the last part. update() allows the entity to control itself, for instance entities will move themselfs based on their current velocity. EntityManager gives the entities being updated methods to call back to game engine to manipulate other entities in the game. For instance, if a rock detects that it hits something it will want to remove the entity that was hit. render() allows the entity to call what ever OpenGL funciton it needs to draw itself to the screen.
The other methods are entity specific. The getSize(), getX() and getY() allow other classes to interegate the position and size of the entity for collision purposes. These methods are utilised in the collides() method to work out whether one entity collides with another.
Finally, entities can be informed that they've collided with another entity through the collide() method. This gives the entity a chance to define some logic that will be enacted when a collision occurs. We reuse the EntityManager interface here to allow the entity to manipulate the game world when it collides with another.
So, what implements EntityManager and what calls render(), update() and checks collisions? We briefly looked at the InGameState class which also acts at the holder for all the game data and runs the engine that handles the interactions between the player and the game world.
When the InGameState is rendered it simply renders a background, renderers all the entities in the game by calling render() on them, and then renders the GUI over the top. It looks like this:
drawBackground(window); // position the view a way back from the models so we // can see them GL11.glTranslatef(0,0,-50); // loop through all entities in the game rendering them for (int i=0;i<entities.size();i++) { Entity entity = (Entity) entities.get(i); entity.render(); } drawGUI(window);
The drawBackground() method simply draws a single quad over the background textured with a spacey image. Next we cycle through the entity list held in the InGameState rendering each one in turn. Finally, we draw the GUI (the score, lifes etc) over the top. Rendering complete!
Next lets look at how the engine handles updating the entities and handling collisions:
public void update(GameWindow window, int delta) { if (gameOver) { gameOverTimeout -= delta; if (gameOverTimeout < 0) { window.changeToState(MenuState.NAME); } } for (int i=0;i<entities.size();i++) { Entity entity = (Entity) entities.get(i); float firstSize = entity.getSize(); for (int j=i+1;j<entities.size();j++) { Entity other = (Entity) entities.get(j); if (entity.collides(other)) { entity.collide(this, other); other.collide(this, entity); } } } entities.removeAll(removeList); entities.addAll(addList); removeList.clear(); addList.clear(); // loop through all the entities in the game causing them // to update (i.e. move, shoot, etc) int rockCount = 0; for (int i=0;i<entities.size();i++) { Entity entity = (Entity) entities.get(i); entity.update(this, delta); if (entity instanceof Rock) { rockCount++; } } if (rockCount == 0) { level++; spawnRocks(level); } }
So, whats going on here? The first bits pretty simple, if the flag to indicate the game is over has been set then after a certain timeout skip back to MenuState (note, we haven't looked at this state yet, its a finishing touch we'll cover later). The next loop cycles through every entity in the game world checking its collision against every other entity in the game world. Not very efficent huh? Absolutely right, but its simple to code and maintain and for our purposes here isn't going to have a high performance hit (we're only talking about 20 odd entities). Weighing up code simplicity against performance it makes more sense to leave this simple, after all, if it does cause a problem we can always change it later. The collision code itself is inside the AbstractEntity (since all entities are collision checked in the same way):
public boolean collides(Entity other) { // We're going to use simple circle collision here since we're // only worried about 2D collision. // // Normal math tells us that if the distance between the two // centres of the circles is less than the sum of their radius // then they collide. However, working out the distance between // the two would require a square root (Math.sqrt((dx*dx)+(dy*dy)) // which could be quite slow. // // Instead we're going to square the sum of their radius and compare // that against the un-rooted value. This is equivilent but // much faster // Get the size of the other entity and combine it with our // own, giving the range of collision. Square this so we can // compare it against the current distance. float otherSize = other.getSize(); float range = (otherSize + getSize()); range *= range; // Get the distance on X and Y between the two entities, then // find the squared distance between the two. float dx = getX() - other.getX(); float dy = getY() - other.getY(); float distance = (dx*dx)+(dy*dy); // if the squared distance is less than the squared range // then we've had a collision! return (distance <= range); }
Well, read the comment above. What we're basically doing here is modelling the collision of the entities as circles colliding on a 2D plane. The game itself is really 2D so this is fine. The one nice optimisation here is not bothering with the square root to get the distance - since in this case we only want to know that its less than the radius of the two circles. This ends up being a nice fast bit of code. So, looking back at our loop - we cycle through each entity, calling collides() on every other entity, which does this speedy check to work out if there's been a collision. Finally, if we detect a collision we notify both entities to let them do what ever work they need.
Ok, so we cycle through letting the entities know if they collided with each other... but whats the addList and removeList stuff about? Remember we allowed collision and update of entities to manipulate the game world through the EntityManager interface. The problem being that is the game world changes while we're looping through that could get our loop out of synch - the collision handler might, for instance, remove an entity that we're about to process! So, when the entities call back to remove or add an entity we don't actually perform the operation immediately. We just add the remove or add request to a list which we execute once per loop. Heres the code for addEntity and removeEntity:
public void removeEntity(Entity entity) { removeList.add(entity); } public void addEntity(Entity entity) { addList.add(entity); }
So, it doesn't really do much.. but combined with the following lines from update():
entities.removeAll(removeList); entities.addAll(addList); removeList.clear(); addList.clear();
the adds and removes get executed without interfering with any loops we might br processing at the time. There are more complicated and intricate ways of dealing with this sort of interaction but this way is pretty simple and does the job for us here just fine.
Right, back to update().. the last thing we do is cycle through all the entities allowing them to update given the amount of time thats passed in from the game framework. While we're updating we look for entities that are rocks/asteroids.. this is just a game mechanic. If there are no rocks left then the level is complete and the player needs some more to shoot!
The only thing we've got to look at now for our data model is the implementations of our Entities.
As mention above each of the entities extends some common functionality in the abstract base class and then adds its own specifics.
Abstract Entity is going to contain the common bits of functionality between all entities. We already know thats includes collision checking (see above) but what else? Well, every entity in the game is going to move so they all need a position and a velocity and when they're updated the position needs to be changed based veclotiy, like so:
public void update(EntityManager manager, int delta) { // update the position of this entity based on its current // velocity. positionX += (velocityX * delta) / 1000.0f; positionY += (velocityY * delta) / 1000.0f; // if we move off either side of the player area, then come back on // the other side. In asteroids all entities have this behaviour if (positionX < -HALF_WIDTH) { positionX = HALF_WIDTH - 1; } if (positionX > HALF_WIDTH) { positionX = -(HALF_WIDTH - 1); } // same again but for top and bottom this time if (positionY < -HALF_HEIGHT) { positionY = HALF_HEIGHT - 1; } if (positionY > HALF_HEIGHT) { positionY = -(HALF_HEIGHT - 1); } } public float getX() { return positionX; } public float getY() { return positionY; }
The first bit is pretty simple, update the position based on the amount of time thats passed and the current velocity. Notice we divide by 1000.0 since the time is in milliseconds and we'd like to specify velocity in units per second.
The next bit checks if the entity has moved off the edge of the screen. If it has, it gets put on the opposite side. This gives the classic asteroids wrap around. The great bit is that because we define this behaviour up in our abstract class it applies to all entities giving us a nice consistent feel.
So, all entities will now move and collide. Great!
The player entity is represented by a space ship which the player can control. In this case we're going to use keyboard control so in the Player entity's update method we check the keys and update velocities like this:
public void update(EntityManager manager, int delta) { // if the player is pushing left or right then rotate the // ship. Note that the amount rotated is scaled by delta, the // amount of time that has passed. This means that rotation // stays framerate independent if (Keyboard.isKeyDown(Keyboard.KEY_LEFT)) { rotationZ += (delta / 5.0f); } if (Keyboard.isKeyDown(Keyboard.KEY_RIGHT)) { rotationZ -= (delta / 5.0f); } // recalculate the forward vector based on the current // ship rotation forwardX = (float) Math.sin(Math.toRadians(rotationZ)); forwardY = (float) -Math.cos(Math.toRadians(rotationZ)); // count down the timer until a shot can be taken again // if the timeout has run out (<= 0) then check if the player // wants to fire. If so, fire and then reset the timeout shotTimeout -= delta; if (shotTimeout <= 0) { if (Keyboard.isKeyDown(Keyboard.KEY_SPACE)) { fire(manager); shotTimeout = shotInterval; } } // if the player is pushing the thrust key (up) then // increse the velocity in the direction we're currently // facing if (Keyboard.isKeyDown(Keyboard.KEY_UP)) { // increase the velocity based on the current forward // vector (note again that this is scaled by delta to // keep us framerate independent) velocityX += (forwardX * delta) / 50.0f; velocityY += (forwardY * delta) / 50.0f; // since we're thrusting now we need to add some effect // to our engine engine trail - add a particle to the // engine positioning it just behind the ship float flameOffset = 1.1f; engine.addParticle(positionX-(forwardX*flameOffset), positionY-(forwardY*flameOffset), 0.6f, 150); } // call the update the abstract class to cause our generic // movement and anything else the abstract implementation // provides for us super.update(manager, delta); // update the particle engine to cause the particles to fade // out over their lifespan. engine.update(delta); }
First, if the player is holding left or right then update the rotation of the ship appropriately. Next we work out the vector that is forward based on the current rotation. This allows us to add velocity in the correct direction should the user be pushing up (thrust). We also check if the player is pressing space - if they are we want the ship to fire and hence create an Shot entity. However, we don't want the player to be able to fire constantly so we add a counter that will count down between the player's allowed fire rate (shotTimeout).
Next, we call the super class implementation to actually move the player entity based on a potentially updated velocity - which uses the AbstractEntity implementation we saw above.
Finally, we call update on the particle engine being rendered for this entity (the ship's engine trail) - we look at the details of this in Part 4.
The Player specific code is the response to collision, heres how its implemented:
public void collide(EntityManager manager, Entity other) { // if we've collide with a rock then the rock must split apart, // and our velocity needs to be changed to push us away from the // rock if (other instanceof Rock) { velocityX = (getX() - other.getX()); velocityY = (getY() - other.getY()); ((Rock) other).split(manager, this); // notify the class manging the entities that the player // has been hit, just in case anything needs doing manager.playerHit(); } }
Pretty easy? If the player hits a rock, change the velocity so the ship bounces off, cause the rock to split in two and finally notify the manager that the player has be hit (so it can cause effects - like sounds).
Shots are really simple, they keep moving until they hit something or they timeout. So the update() method is nice and easy:
public void update(EntityManager manager, int delta) { // cause the particle to move by calling the abstract super // class's implementation of update super.update(manager, delta); // update the amount of time left for this shot to exist life -= delta; if (life < 0) { // if the life time has run out then remove the shot // entity from the game manager.removeEntity(this); } else { // otherwise add another particle to the engine at the // current position and update the particle engine to // cause existing particles to fade out particles.addParticle(getX(), getY(), size, 200); particles.update(delta); } }
Like in player will call the super class implementation of update to move the shot based on its velocity. We decrement the life counter to time out the shot as it flys across the screen. If the shot has run out of life we remove it from the game, otherwise we add another particle to the shot trail.
So, shots move and timeout. The other specific part, as with Player, is the collision handler. Here's the implementation:
public void collide(EntityManager manager, Entity other) { // if the shot hits a rock then we've scored! The rock // needs to split apart and then this shot has been used up // so remove it. if (other instanceof Rock) { ((Rock) other).split(manager, this); manager.removeEntity(this); } }
Yes, its that short! If the shot is notified that its collided with the a rock then we split the rock in to and remove the shot. Now, we can blast rocks!
Our finally entity is the asteroid. There will be a whole group of these floating round the screen. There are a couple of rock specific things going on here. Lets take a look at update():
public void update(EntityManager manager, int delta) { // call the abstract entitie's update method to cause the // rock to move based on its current settings super.update(manager, delta); // the rocks just spin round all the time, so adjust // the rotation of the rock based on the amount of time // that has passed rotationZ += (delta / 10.0f) * rotateSpeed; }
Not much there right? Rocks are special in that we'd like them to rotate as they move, hence hte update to the Z rotation of the rock as we go. As with the other entities we call the super implementation of the update method to get the asteroid to move.
However, rocks have more special functionality.. when they collide, they bounce:
public void collide(EntityManager manager, Entity other) { // if anything collides with a rock its direction must change // (to prevent rocks intersecting with each other). For effect // we'll also change the direction of rotation velocityX = (getX() - other.getX()); velocityY = (getY() - other.getY()); rotateSpeed = -rotateSpeed; }
We don't need to check what the asteroid hit. Whatever it hits its going to bounce off. How then are asteroids destroyed? Well, they have a special method that other entities have already been calling. So, when the player detects that they've hit an asteroid the Player entity calls split() to kill the asteroid. The split method looks like this:
void split(EntityManager manager, Entity reason) { // remove this rock (since its about to split) and notify // the manager that a rock has been destroyed (just in case // it needs to take an action) manager.removeEntity(this); manager.rockDestroyed(size); // if the rock isn't the smallest we'll need to create // two smaller rocks which the rock destroyed have "split" // into if (size > 1) { // work out the vector at which the reason for this rock // split hit the rock in question float dx = getX() - reason.getX(); float dy = getY() - reason.getY(); // scale it down a bit since we're about to use it // for position the split out rocks. The smaller the original // rock the closer the resulting rocks need to be to each other dx *= (size * 0.2f); dy *= (size * 0.2f); // the speed that the rocks splitting out are going to be sent // out at. This value has no units and is just a scalar used // to tune the game float speed = 2; // create and add the two new rocks based on the direction of // impact and the size of the new rocks. Rock rock1 = new Rock(texture, model, getX() + dy, getY() - dx, size - 1, dy * speed, -dx * speed); Rock rock2 = new Rock(texture, model, getX() - dy, getY() + dx, size - 1, -dy * speed, dx * speed); manager.addEntity(rock1); manager.addEntity(rock2); } }
When we split a rock we first remove the existing rock since its about to explode. Next we check if it should turn into smaller rocks by checking its size. If its big enough we create two new rocks of a smaller size and add the to the game world using the EntityManager (which of course is actually the InGameState). Another nice small piece of code specific to the job at hand.
If you've followed the first 2 parts of this tutorial we've now talked about how to get a window on the screen, build and simple framework and code up the mechanics of the asteroids game. However, up to this point we've not really looked at how we visualise anything. This part of the tutorial will cover how we load up 3D models and display them to represent the player and the asteroid entities we looked at in Part 1.
3D Models are generally loaded from files created in other tools. For instance, the player models in Quake were often created with "QME" and then loaded into the game from MD2 files. For this tutorial we're going to look at Wavefront Object Files. They generally use the extension ".obj". This format was chosen because of its popularity as a export format for modelling tools. Pretty much any modelling tool will export to OBJ format. Its also a particularlly easy format to process.
Model file formats and generally documented somewhere - however the search for a reliable and accurate specification often takes longer than writing the code to load it. A good site to try initially is http://www.wotsit.org/. Then tend to have the majority of formats hanging around.
The specification/guide we're going to use for OBJ files is here. Its not a full specification but it gives us enough information to get the features we want. Note that this is an important point to realise. 3D file formats often support a great range of features, normally you only want to utilise a small set of these so you don't really need to cope with everything. Picking and choosing like this can dramatically reduce your development time. There are cases of course where a full feature set should be supported, for instance if you happen to be writing a generic loader for a scenegraph (Xith3D, JME).
Before we get working on the loader thats going to render the models in OpenGL we need to
be familiar with the format. Lets take a look at what our format specification says:
v x y z The vertex command, this specifies a vertex by its three coordinates. The vertex is implicitly named by the order it is found in the file. For example, the first vertex in the file is referenced as '1', the second as '2' and so on. None of the vertex commands actually specify any geometry, they are just points in space. vt u v [w] The vertex texture command specifies the UV (and optionally W) mapping. These will be floating point values between 0 and 1 which say how to map the texture. They really don't tell you anything by themselves, they must be grouped with a vertex in a 'f' face command. vn x y z The vertex normal command specifies a normal vector. A lot of times these aren't used, because the 'f' face command will use the order the 'v' commands are given to determine the normal instead. Like the 'vt' commands, they don't mean anything until grouped with a vertex in the 'f' face command. f v1[/vt1][/vn1] v2[/vt2][/vn2] v3[/vt3][/vn3] ... The face command specifies a polygon made from the verticies listed. You may have as many verticies as you like. To reference a vertex you just give its index in the file, for example 'f 54 55 56 57' means a face built from vertecies 54 - 57.
So, the OBJ file format is text based and consists of 4 types of definition. The first defines vertices (or points in space). This is signified by a line starting with a "v" followed by 3 numbers indicating the x, y and z coordinate of the point in space. The next definition is signified by "vt", standing for vertex texture. The two numbers following the indicator define a texture coordinate. The 3rd definition, "vn", defines a vertex normal - a single normal that can be applied to a vertex.
The 4th and most important definition is signified by "f" standing for face. The list of numbers after the "f" indicate which vertex, vertex texture coordinate and vertex normal should be combined to form a face. For instance, the following line:
f 1/3/3 2/4/4 3/5/5
indicates that a face should be built with 3 points. The first point uses the 1st vertex defined in the file, the 3rd texture coordinate and the 3rd normal. The second point used the 2nd vertex defined in the file, the 4th texture coordinate and the 4th normal. The final point uses the 3rd vertex defined in the file and the 5th texture coordinate and normal.
Now we have a feel for how the format works lets consider how we're going to load the data.
The pseudo code for loading a OBJ file looks like this:
while there are more lines to read read a line if the line starts with a "v" then read the vertex and store it if the line starts with a "vt" then read the texture coordinate and store it if the line starts with a "vn" then read the normal coodinate and store it if the line starts with a "f" then read the indexes and store a face based on the data read
Lets put this into real Java code then.. What this code is actually going to do is just read the data and store it for rendering use. The class in question is ObjData. Take a look at the constructor, its responsible for executing the pseudo code above:
public ObjData(InputStream in) throws IOException { // read the file line by line adding the data to the appropriate // list held locally BufferedReader reader = new BufferedReader(new InputStreamReader(in)); while (reader.ready()) { String line = reader.readLine(); // if we read a null line thats means on some systems // we've reached the end of the file, hence we want to // to jump out of the loop if (line == null) { break; } // "vn" indicates normal data if (line.startsWith("vn")) { Tuple3 normal = readTuple3(line); normals.add(normal); // "vt" indicates texture coordinate data } else if (line.startsWith("vt")) { Tuple2 tex = readTuple2(line); texCoords.add(tex); // "v" indicates vertex data } else if (line.startsWith("v")) { Tuple3 vert = readTuple3(line); verts.add(vert); // "f" indicates a face } else if (line.startsWith("f")) { Face face = readFace(line); faces.add(face); } } // Print some diagnositics data so we can see whats happening // while testing System.out.println("Read " + verts.size() + " verticies"); System.out.println("Read " + faces.size() + " faces"); }
So we read each line, processing it and storing each of the vertex related bits of data into some lists stored in the ObjData instance. A "tuple" is just a term for a group of values that are related to each other. So, a Tuple2 in this case is used for the pair of values used for a vertex coordinate and a Tuple3 is used for normals and verticies. The utility methods to read these tuples simply split the remainder of the line on spaces.
Finally, if we read a face we're going to construct a record describing the face and store in another list. This way our ObjData simply contains all of the data stored in the OBJ file but in a format that can be used by other Java objects easily. The creation of the face object is handled in the readFace() method:
private Face readFace(String line) throws IOException { StringTokenizer points = new StringTokenizer(line, " "); points.nextToken(); int faceCount = points.countTokens(); // currently we only support triangels so anything other than // 3 verticies is invalid if (faceCount != 3) { throw new RuntimeException("Only triangles are supported"); } // create a new face data to populate with the values from the line Face face = new Face(faceCount); try { // for each line we're going to read 3 bits of data, the index // of the vertex, the index of the texture coordinate and the // normal. for (int i=0;i<faceCount;i++) { StringTokenizer parts = new StringTokenizer(points.nextToken(), "/"); int v = Integer.parseInt(parts.nextToken()); int t = Integer.parseInt(parts.nextToken()); int n = Integer.parseInt(parts.nextToken()); // we have the indicies we can now just add the point // data to the face. face.addPoint((Tuple3) verts.get(v-1), (Tuple2) texCoords.get(t-1), (Tuple3) normals.get(n-1)); } } catch (NumberFormatException e) { throw new IOException(e.getMessage()); } return face; }
First, note that the code assumes that only triangles will be supported (i.e. only 3 points per face). For our purposes here we only need triangles (all the models are carefully designed to only use triangles) - so we to save time we've skipped anything else that the model format might support. However, to be on the safe side, if a model with more than 3 points in a face is read in we'll throw a RuntimeException - it should be pretty obvious if anything goes wrong :)
Next we read in the index for each component of each point in the face, look them up in our lists and store them in a Face object which acts a data record.
So, after the ObjData has processed the input stream containing the OBJ file, the lists in the ObjData instance have been populated with all the data for the model. This object can now be used to create the OpenGL expressions to render the model to the screen.
Now we've got all the data out of the OBJ model we want to be able to render our triangles in OpenGL. We could simply do this in immediate mode (i.e. lots of calls to glVertex() etc at runtime) but thats not very good for performance. Since we know our models arn't going to change as we render them we can compile a OpenGL display list containing the model which we can render with one command!
A display list is a list of OpenGL operations identified by a single value. A developer creates a list and then issues the commands required to be contained with in it. The OpenGL implementation can them optimize these commands and store them on the graphics hardware. Consider that every command you issue to OpenGL must make it to the hardware, so the more commands you have to issue the more has to be sent to the card, and hence the slower things happen. With a good OpenGL implementation the display list will be completely optimised onto the card so we only have to issue a single command to the graphics hardware to get it to execute a whole list of commands.
Right, lets have a look at how will build the display list from our ObjData instance. It all happens in the ObjModel :
public ObjModel(ObjData data) { // we're going to process the OBJ data and produce a display list // that will display the model. First we ask OpenGL to create // us a display list listID = GL11.glGenLists(1); // next we start producing the contents of the list GL11.glNewList(listID, GL11.GL_COMPILE); // cycle through all the faces in the model data // rendering a triangle for it GL11.glBegin(GL11.GL_TRIANGLES); int faceCount = data.getFaceCount(); for (int i=0;i<data.getFaceCount();i++) { for (int v=0;v<3;v++) { // a position, normal and texture coordinate // for each vertex in the face Tuple3 vert = data.getFace(i).getVertex(v); Tuple3 norm = data.getFace(i).getNormal(v); Tuple2 tex = data.getFace(i).getTexCoord(v); GL11.glNormal3f(norm.getX(), norm.getY(), norm.getZ()); GL11.glTexCoord2f(tex.getX(), tex.getY()); GL11.glVertex3f(vert.getX(), vert.getY(), vert.getZ()); } } GL11.glEnd(); GL11.glEndList(); }
First, we create a display list by calling glGenLists(). We store the ID of the list locally so we can use it later on. Next, we start building the list wiht glNewList() and then process the ObjData we read from the file earlier.
For each face we found in the OBJ model we create a single triangle in a triangles array in OpenGL. The triangle is defined by applying the normal, texture coordinate and vertex information store in the face that we created earlier.
Once we've processed all the faces we're done so will close off the triangle array and end the list compilation. At this point the OpenGL implemention (and related hardware) can optimize the commands and ship them over to the hardware. So, how do we actually render the model? Well, thats the easy bit:
public void render() { // since we rendered our display list at construction we // can now just call this list causing it to be rendered // to the screen GL11.glCallList(listID); }
We simply call our list and OpenGL executes all the operations we set up earlier (based on an OBJ model).
Cool, now we can load models and render them in OpenGL. However, its a little intricate to create ObjData object then pass that into a ObjModel and also write the code to get an InputStream. We could make it a bit easier by writing a simple static utility method that performs the common bits for us based on a simple reference to a model. Take a look a ObjLoader, tihs is where we've put that static method. It looks like this:
public static ObjModel loadObj(String ref) throws IOException { InputStream in = ObjLoader.class.getClassLoader().getResourceAsStream(ref); if (in == null) { throw new IOException("Unable to find: "+ref); } return new ObjModel(new ObjData(in)); }
Simply look up the reference specified on the class path. Check if the reference was actually found. Finally call the loading classes we've talked about above to load the data and then render it to a display list.
Now we can load models in one line. Nice!
The game uses the model loader to load the models for both asteroids and the player. However, the we don't want to load the model on a per entity basis since that could take up a lot of memory and the model can happily be shared. So, for instance, in InGameState we load the player model like so:
shipTexture = loader.getTexture("res/ship.jpg"); shipModel = ObjLoader.loadObj("res/ship.obj");
and then we pass the model and texture into the player entity when we create it:
public void enter(GameWindow window) { entities.clear(); player = new Player(shipTexture, shipModel, shotTexture);
Finally, when the Player entity is rendered we simply bind our texture (since we want our model to be textured) then call our list (which contains our polys with texture coordinates), like this:
public void render() { // enable lighting for the player's model GL11.glEnable(GL11.GL_LIGHTING); // store the original matrix setup so we can modify it // without worrying about effecting things outside of this // class GL11.glPushMatrix(); // position the model based on the players currently game // location GL11.glTranslatef(positionX,positionY,0); // rotate the ship round to our current orientation for shooting GL11.glRotatef(rotationZ,0,0,1); // setup the matrix to draw the model for our player // rotate the ship to the right orientation for rendering. Our // ship model is modelled on a different axis to the one we're // using so we'd like to rotate it round GL11.glRotatef(90,1,0,0); // scale the model down because its way to big by default GL11.glScalef(0.01f,0.01f,0.01f); // bind to the texture for our model then render it. This // actually draws the geometry to the screen texture.bind(); model.render();
First, we move to the right location to place the player based on its current position and orientation. Next we scale the model down (because its a bit big to start with). Finally, we do the actual rendering code. We bind to the texture (so the model appears with it) - texture.bind(), and then render the model by calling model.render() (which calls the display list as we saw earlier).
Now, we can render the models for our entities as they bounce through space. We've talked about model formats, reading data and rendering the polygons. We've also had a quick look at how the game uses the models after they've been loaded.
If you've followed through the first 3 parts of the tutorial we should now understand how the game framework works, how the window is display, how the game logic is abstract from the rendering and in the last part how we display 3D models for each of the game entities.
This part of the tutorial is going to begin looking at the smaller features that make a game feel more complete. First we'll take a look at sound effects. In this case we've chosen to use OGG files. OGGs are a bit like MP3s apart from without any of the lawful restrictions on use. Luckily enough there is a Java implementation of OGG decoding freely available called JOrbis. Since we'll be playing the sound effects through LWJGL's binding to OpenAL we're going to need the sound effect data in "PCM" format. This is exactly what JOrbis does for us, it takes OGG encoded data and decodes it into PCM. We'll look at decoding the OGG using JOrbis and playing it through OpenAL.
The second part of this tutorial will look at displaying some GUI type graphics. These will allow us to display a game menu before the game starts and display the score and lives while the game is playing. This is really very much like what you might have seen in the Space Invaders tutorials.
First lets start by saying using JOrbis properly is pretty tricky. Luckily the nice people at JCraft provide example code in the JOrbis package which is what we've used here. Take a look at the OggDecoder. Its a monster! The decoder opens the input stream, checks the file is a indeed an Ogg Vorbis and then proceeds to cycle through the pages in the file decoding them into PCM data.
The important thing to note here is that once our method is finished we either end up with a failure or an instance of the OggData which describes the PCM data pulled out of the OGG. OggData looks like this:
class OggData { /** The data that has been read from the OGG file */ public ByteBuffer data; /** The sampling rate */ public int rate; /** The number of channels in the sound file */ public int channels; }
What we've pulled out here is exactly what we need to play the sound through OpenAL. The data buffer contains the PCM data that been decoded. The rate tells us the sampling rate of the OGG (this can vary and hence needs to be passed to OpenAL). Likewise, channels specifies the number of channels in the OGG, which again OpenAL needs to play the PCM.
Since we've now got our PCM data all we need to do is pipe it into OpenAL to get it to play. Well, its a little more complicated, lets take a look at SoundLoader.
There are two initial concepts we have to grasp about OpenAL. First, there are buffers, these are used to store the batchs of PCM data which are our sound effects or music. The number of these that you hold is simply memory dependent.
Second, OpenAL has the concept of sources, these are used to actually play the sound effects. Think of them as the source of the sound effect or music. A source can only play a single sound at any given time. The number of sources that can be used is dependent on your user's sound hardware. To assume 16 sound sources is pretty safe with todays players on the PC. However, choosing to go for 8 source is safer and is normally enough to give the feel of multiple sound effects going off at the same time. Note that you'll normally want to reserve one of your sources for music playing at all times. This allows you to keep the music playing while shooting off the sound effects.
Right, lets go back and take a look at SoundLoader initialisation.
public void init() { inited = true; try { AL.create(); soundWorks = true; sounds = true; } catch (Exception e) { e.printStackTrace(); soundWorks = false; sounds = false; } if (soundWorks) { sourceCount = 8; sources = BufferUtils.createIntBuffer(8); AL10.alGenSources(sources); if (AL10.alGetError() != AL10.AL_NO_ERROR) { sounds = false; soundWorks = false; } } }
We use the LWJGL binding to initialise the OpenAL driver. Next we try to allocate 8 sources - we're trying to be on the safe side here. Note how we record if anything goes wrong. If sounds don't initialise properly we'd rather not stop the game, it would be better to just not play the sounds.
Right, we've got 8 sources to play sounds on, how are we going to use the OGG decoder to get our buffers.. lets take a look at the getOgg() method:
public Sound getOgg(String ref) throws IOException { if (!soundWorks) { return new Sound(this, 0); } if (!inited) { throw new RuntimeException("Can't load sounds " + "until SoundLoader is init()"); } int buffer = -1; if (loaded.get(ref) != null) { buffer = ((Integer) loaded.get(ref)).intValue(); } else { System.out.println("Loading: "+ref); try { IntBuffer buf = BufferUtils.createIntBuffer(1); InputStream in = Thread.currentThread(). getContextClassLoader(). getResourceAsStream(ref); OggDecoder decoder = new OggDecoder(); OggData ogg = decoder.getData(in); AL10.alGenBuffers(buf); AL10.alBufferData(buf.get(0), ogg.channels > 1 ? AL10.AL_FORMAT_STEREO16 : AL10.AL_FORMAT_MONO16, ogg.data, ogg.rate); loaded.put(ref,new Integer(buf.get(0))); buffer = buf.get(0); } catch (Exception e) { e.printStackTrace(); Sys.alert("Error","Failed to load: "+ref+" - "+e.getMessage()); System.exit(0); } } if (buffer == -1) { throw new IOException("Unable to load: "+ref); } return new Sound(this, buffer); }
So, what are we doing here? First, if the sound system is working just return an empty sound object. The object will never actually do anything so its juts nice and quick to return an empty object. Next, if we haven't already initialised the sound manager then fail because there must have been a programming error.
Now we actually start loading things. If we've already loaded the OGG once return the preloaded buffer identifier. If we haven't loaded it before use the OggDecoder to get the PCM data from the specified reference. Next we ask OpenGL to create us a new buffer (alGenBuffer()) and then fill the buffer with the data we've loaded from the OGG file (alBufferData()). Finally, we cache the buffer just incase someone trys to load the same one again and pass back a sound object that holds a reference to the buffer we've created.
At the end we have a Sound object that contains enough information to be able to play the sound effect or music thats loaded from the OGG. We pass this back to the caller. We can now use the sound object to play the sound by calling:
public void play(float pitch, float gain) { store.playAsSound(buffer, pitch, gain); }
The sound object simply calls back to the SoundManager with its buffer. In SoundManager playAsSound() looks like this:
void playAsSound(int buffer,float pitch,float gain) { if (soundWorks) { if (sounds) { nextSource++; if (nextSource > 7) { nextSource = 1; } AL10.alSourceStop(sources.get(nextSource)); AL10.alSourcei(sources.get(nextSource), AL10.AL_BUFFER, buffer); AL10.alSourcef(sources.get(nextSource), AL10.AL_PITCH, pitch); AL10.alSourcef(sources.get(nextSource), AL10.AL_GAIN, gain); AL10.alSourcePlay(sources.get(nextSource)); } } }
Here we can see actually playing the source effect. If the sound system is working and sounds are turned on we first find a source to play the effect through. When we start playing a sound through a source any sound currently being played on that source will stop. So, as we play more sound effects we want to cycle through our sources to try and prevent collisions. We choose the next source, stop any sound playing on it by calling alSourceStop(), then load it with the sound effect buffer we want to use (alSourcei()). Finally we request that the source starting playing the buffer its loaded with (alSourcePlay()) - and there we go, the sound starts playing!
Now we can play sounds where are we going to use them in the game? It'd be nice to have a sound when the play fires (its traditional after all) and maybe one when an asteroid is blown up. To achieve this we need the entities to be able to feed back to the central game logic (InGameState) so that it can play the sounds when events occur. This is done using our EntityManager interface again, with the follow methods:
/** * Notification that a rock has been destroyed * * @param size The size of the rock that was destroyed */ public void rockDestroyed(int size); /** * Notification that the player fired a shot */ public void shotFired();
Since the EntityManager is passed into the entity logic and collision (see Part 2) we can notify the central game logic when things occur. Lets look at how these things are implement in InGameState. First, heres the init() method where we load up the sound effects:
public void init(GameWindow window) throws IOException { ... shoot = SoundLoader.get().getOgg("res/hit.ogg"); split = SoundLoader.get().getOgg("res/bush.ogg"); }
We simply load up the sounds and store them locally to the instance. Now, lets look at the implementation of the notification methods on InGameState:
public void rockDestroyed(int size) { split.play(1.0f,1.0f); score += (4 - size) * 100; } public void shotFired() { shoot.play(1.0f,0.5f); }
What we've done is built a nice simple utility for loading and playing sound effects. As you can see, from a utility users point of view loading a sound effects is a one line, and playing yet another one line. Great! So, when our entities detect that a rock was destroyed (Rock.split()) or that that a shot was fired (Player.update()) they notify the game logic and it in turns plays an appropriate sound effect (which will only get played if the sound system is working).
If we look at InGameState we can see that the during the game play the player has some things they have to think about:
/** The current score */ private int score; /** The number of lifes left */ private int life = 4;
The player accumulates score as they play and has a number of lifes - or times they can hit an asteroid without dieing. Lets start with the score, we want to render the numbers to the screen. If we were working in Java2D we could just use a font and render the text. However, OpenGL doesn't provide any default text writing capabilities so we're going to have to write a Font class to render the text.
Fonts can be rendered in OpenGL in lots of different ways. The easiest by far is to simply use a texture with the characters in it and map them onto OpenGL quads on the screen. This way you can simply choose the character based on the texture coordinates. Here's an example of a Font Texture. Be warned its alpha based so it may appear as a white only image. The image is actually a set of white character layed out to be mapped easily onto the screen. Theres a great tool for doing this called the Bitmap Font Builder. It can take any windows font and produce a font texture from it.
Right, so we've got our font layed out onto an image how are we going to use it? Lets take a look at the BitmapFont class. This one loads up a font texture and allows us to write strings to the screen using it. Loading up the texture looks like this:
public BitmapFont(Texture texture, int characterWidth, int characterHeight) { this.texture = texture; this.characterWidth = characterWidth; this.characterHeight = characterHeight; // calculate how much of the texture is taken up with each character // by working out the proportion of the texture size that the character // size in pixels takes up characterWidthInTexture = texture.getWidth() / (texture.getImageWidth() / characterWidth); characterHeightInTexture = texture.getHeight() / (texture.getImageHeight() / characterHeight); // work out the number of characters that fit across the sheet charactersAcross = texture.getImageWidth() / characterWidth; // chosen an arbitary value here to move the letters a bit // closer together when rendering them characterStep = characterWidth - 5; }
So, we're provided with the texture and told how big each character is on the font sheet. Based on this we can work out how much as a fraction of the texture each character takes up and hence how far along in texture coordinates we need to move to get to each character. Sounds a bit complicated? Think about it as taking a square and cutting it up into a grid. We know that in texture coordinates the whole square is 1.0 is width. If there are 16 characters across the square then each character takes up 1.0 divided by 16.0 texture coordinates up.
Right, so how do we use this fact to draw the string? Now this gets a bit tricky, heres the rendering code:
public void drawString(int font, String text, int x, int y) { // bind the font text so we can render quads with the characters // on texture.bind(); // turn blending on so characters are displayed above the // scene GL11.glEnable(GL11.GL_BLEND); GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA); // cycle through each character drawing a quad to the screen // mapped to the right part of the texture GL11.glBegin(GL11.GL_QUADS); for (int i=0;i<text.length();i++) { // get the index of the character baesd on the font starting // with the space character int c = text.charAt(i) - ' '; // work out the u,v texture mapping coordinates based on the // index of the character and the amount of texture per // character float u = ((c % charactersAcross) * characterWidthInTexture); float v = 1 - ((c / charactersAcross) * characterHeightInTexture); v -= font * 0.5f; // setup the quad GL11.glTexCoord2f(u, v); GL11.glVertex2i(x+(i*characterStep), y); GL11.glTexCoord2f(u, v - characterHeightInTexture); GL11.glVertex2i(x+(i*characterStep), y+characterHeight); GL11.glTexCoord2f(u + characterWidthInTexture, v - characterHeightInTexture); GL11.glVertex2i(x+(i*characterStep)+characterWidth, y+characterHeight); GL11.glTexCoord2f(u + characterWidthInTexture, v); GL11.glVertex2i(x+(i*characterStep)+characterWidth, y); } GL11.glEnd(); // reset the blending GL11.glDisable(GL11.GL_BLEND); }
Lets take this in small steps. The first thing we do is bind to the font texture since this is what we want to draw with this makes sense. Next we enable blending - this allows us to draw the text to the screen but not draw the parts of the texture whose alpha value is 0. Remember we looked at the font texture itself above, the background has an alpha of 0 so enabling blending like this will allow us to draw the texture as through its overlayed over the image.
Next we start drawing the actual string. We're going to draw quad per character and map the appropriate part of the texture onto the quad. So for each character we draw a quad at the right place on the screen. This is based on the coordinates we want to draw the string at and a bit of space for each character drawn (so we move left as we're writing characters). The important bit to note here is how we caculate the texture coordinates.
float u = ((c % charactersAcross) * characterWidthInTexture); float v = 1 - ((c / charactersAcross) * characterHeightInTexture);
So the u (horizontal texture coordinate) value is caculated by finding the modulus (or remainder) of the character we're looking for against the number of characters across the font sheet. We take this value and multiply by how much of the texture is taken up by each character horizontally. The v (vertical texture coordinate) value is the calculated in a similar way accept that we use division to work out which row the character appears on. The "one minus" at the start of the caculation is due to the way that textures are being loaded in. They're actually up-side down so this "one minus" offsets the texture coordinate appropriately.
How about a worked example. Lets say we want character 21 (maybe its "A"). The font texture has 16 character across so its column will be 21 % 16 which comes to 5. The row will be 21 / 16 which equals 1. So we want the character at 5,1 on the sheet. Multiply these values up by the texture coordinate ratio and we're at the right position on the texture!
Finally we draw the quad using the texture coordiantes we've generated and hence the character is displayed on the screen.
The first simple way the font is use is to display the score and lives in the InGameState. Lets take a look at loading the font in init() first:
public void init(GameWindow window) throws IOException { ... Texture fontTexture = loader.getTexture("res/font.png"); font = new BitmapFont(fontTexture, 32, 32); ... }
We load up the font texture in the normal way and create a font object based on the texture and knowledge that the characters on the sheet are 32 pixels by 32 pixels. As we've seen all the calculations go on inside the BitmapFont object to work out how to display the text.
Now we need to render the text, during the render() method we call drawGUI() which looks like this:
private void drawGUI(GameWindow window) { window.enterOrtho(); GL11.glColor3f(1,1,0); font.drawString(1, "SCORE:" + score, 5, 5); GL11.glColor3f(1,0,0); String lifeString = ""; for (int i=0;i<life;i++) { lifeString += "O"; } font.drawString(0, lifeString, 795 - (lifeString.length() * 27), 5); GL11.glColor3f(1,1,1); if (gameOver) { font.drawString(1, "GAME OVER", 280, 286); } window.leaveOrtho(); }
We finally get to use our GameWindow utility methods that we saw in Part 1 of the tutorial! The first thing we do here is to enter orthographic projection mode - this allows us to draw our text in 2D style coordinates as we talked about in Part 1. Next we set the color to yellow and use the font to draw the current score in the top left corner of the screen. Then we create a string that contains one zero for each life remaining, set the color to red and draw this string in the top right corner. If the player has lost and the game is over we display the "GAME OVER" message in the middle of the screen in white.
Finally and importantly, we leave orthographic projection mode which resets everything to how it was before we entered this method.
Yet again, once we've written utilities to perform bitmap font rendering the actual code to draw text to the screen becomes very simple. This is generally what we should be aiming for, one time complexity reused over and over in very simple ways.
The other major place where we've used the font rendering is in the game menu thats been added to the front of the game. To achieve the game menu we've added a second game state called MenuState. This class is strictly responsible for rendering and maintaing the game menu. It utilises the BitmapFont class in its render() method as follows:
/** The options to present to the user */ private String[] options = new String[] {"Start Game", " Exit"}; public void render(GameWindow window, int delta) { GL11.glColor3f(0.2f,0.2f,0.3f); drawBackground(window); window.enterOrtho(); GL11.glColor3f(1f,1f,1f); font.drawString(1, "ASTEROIDS", 280, 210); for (int i=0;i<options.length;i++) { GL11.glColor3f(0.5f,0.5f,0); if (selected == i) { GL11.glColor3f(1,1,0.3f); } font.drawString(0, options[i], 270, 280+(i*40)); } window.leaveOrtho(); }
So, we draw a title in the center of the screen, "ASTEROIDS". Next we cycle through the menu options drawing them in the center of the screen. Note how we change the color when we're about draw the menu option that is currently selected. Also note how all the rendering of the menu is sandwiched between enterOrtho() and leaveOrtho() again.
To complete the picture lets take a look at the update() method that handles the keyboard input on the menu screen and changing state:
public void update(GameWindow window, int delta) { while (Keyboard.next()) { if (Keyboard.getEventKeyState()) { if (Keyboard.getEventKey() == Keyboard.KEY_UP) { selected--; if (selected < 0) { selected = options.length - 1; } } if (Keyboard.getEventKey() == Keyboard.KEY_DOWN) { selected++; if (selected >= options.length) { selected = 0; } } if (Keyboard.getEventKey() == Keyboard.KEY_RETURN) { if (selected == START) { window.changeToState(InGameState.NAME); } if (selected == EXIT) { System.exit(0); } } } } }
In LWJGL the Keyboard class holds a list of events that occured since last LWJGL update(). Here we cycle through these events looking for controls we're interested in. The up and down cursors are used to move the selected option up and down. The return key changes us to the InGameState (i.e. it starts the game) with window.changeToState(InGameState.NAME).
Taking a look a the MenuState we can see how a reasonably short piece of code can actually give us a complete game menu all being rendered in OpenGL.
We're getting close to completion now. If you've been following through the first four parts then well done! We've now looked at getting a window on the screen, building a simple game framework, creating a data model for the game, rendering models, playing sound effects and displaying text and overlays over the game. Our last part is going to add some simple effects to make the whole game feel that bit more glowy!
The term particle engine is thrown around alot in games development with no one being really clear what it is. For our purposes a particle engine is a data structure that maintains a set of points that are effected by "some external force". These points are then rendered as small quads which appear on the screen as blobs of color. Each particle maintains its position, color and faded-ness (alpha) which can be changed by this "external force". Now, the reason we lump the particles together in an engine is two fold.
First, performance, updating and rendering all the particles together can save us time. For instance, rendering all the particles as one large OpenGL vertex array rather than as individual quads can really speed up rendering. Second, particles can interact with each other and maintaining them from a single location makes this very easy.
When particle engines are implemented generically they can be used for some really nice effects. This flame is generated using a generic particle system with some specific parameters. The same particle engine can be used for lazer fire, smoke and explosions.
The particle engine we're going to create for the asetroids game here is alot simpler than a full generic system. Its going to be used to render the lazers fired by the player and a engine glow from the player's ship.
The particle engine for our asteroids game is contained within a single class named ParticleGroup. This class is responsible for holding all the information about the particles, updating it as time goes by and rendering the particles themself through OpenGL. First, lets look at the data thats being held in the class for each particle:
/** The positions of the particles being rendered */ private float[][] pos; /** The life left in each particle being rendered */ private int[] life; /** The initial size of each particle */ private float[] initialSize; /** The current size of each particle */ private float[] size; /** The alpha value of each particle */ private float[] alpha; /** The index of the next particle to be used */ private int next; /** The time particles should take to fade out */ private int fadeOut; /** The red component of the colour of each particle */ private float r; /** The green component of the colour of each particle */ private float g; /** The blue component of the colour of each particle */ private float b;
"pos" stores the position coordinates of each particle. The "life" array stores a counter for each particle that indicate how much longer it has to live, i.e. how much longer in milliseconds it will continue to be updated and rendered. "initialSize" and "size" allow us to scale the particles down as they begin to die. The "alpha" value for each particle defines how faded the particle will be rendered - as particles begin to die we fade them out to prevent them flashing out of existence. The "fadeOut" time defines how long it takes particles to disappear. Finally, the "r", "g" and "b" values define the color that are particle group will take on. In this case we're only going to support one color across all particles.
Right, we understand what data we're holding, lets take a look at creating a group of particles:
public ParticleGroup(int count, int fadeOut, float r, float g, float b) { pos = new float[count][2]; life = new int[count]; size = new float[count]; initialSize = new float[count]; alpha = new float[count]; this.fadeOut = fadeOut; this.r = r; this.g = g; this.b = b; }
Note that we're using arrays for the particles and there are fixed number of particles supported by any group. This is more for the potential gains in performance over using something like a dynamic list. The caller only has the ability to specified very simple parameters, count - the number of particles to be held in the group, fadeOut - the amount of time it takes particles to disappear and r,g,b - the colour of the particles that should be rendered.
Initially there are no particles, heres the method that lets the developer add particles at run time:
public void addParticle(float x, float y, float size, int life) { pos[next][0] = x; pos[next][1] = y; this.size[next] = size; this.initialSize[next] = size; this.life[next] = life; alpha[next] = 1; next++; if (next >= this.life.length) { next = 0; } }
So, each time the entity wants to add an entity it must specify the position at which the particle should appear, its initial size and how long its going to last for. We stick these values into the array entries for the next available particle and increment the next available particle. This isn't the prettiest way to handle particle reuse but it'll work fine for the simple systems we're going to use here.
Heres how the Shot fired from the player uses the particle group in its update:
public void update(EntityManager manager, int delta) { // cause the particle to move by calling the abstract super // class's implementation of update super.update(manager, delta); // update the amount of time left for this shot to exist life -= delta; if (life < 0) { // if the life time has run out then remove the shot // entity from the game manager.removeEntity(this); } else { // otherwise add another particle to the engine at the // current position and update the particle engine to // cause existing particles to fade out particles.addParticle(getX(), getY(), size, 200); particles.update(delta); } }
While the shot is still alive we add a particle at the currently location of the shot and then update the particle engine. This gives a lump of particles in a trail behind the shot making it look like a lump of plasma. The way the particles are rendered the more there are in one space the whiter the particles look - this means that there is a white glow around the front of the shot.
The other place we use the particle group is in the Player entity. Here we leave particles behind the ship as it flys giving us a gentle engine trail. Its done like this in the update() method of Player:
if (Keyboard.isKeyDown(Keyboard.KEY_UP)) { // increase the velocity based on the current forward // vector (note again that this is scaled by delta to // keep us framerate independent) velocityX += (forwardX * delta) / 50.0f; velocityY += (forwardY * delta) / 50.0f; // since we're thrusting now we need to add some effect // to our engine engine trail - add a particle to the // engine positioning it just behind the ship float flameOffset = 1.1f; engine.addParticle(positionX-(forwardX*flameOffset), positionY-(forwardY*flameOffset), 0.6f, 150); }
Here we add particles as the player pushes the thrust button. We actually drop the particles just behind the ship to give the feeling they are being pushed out the engine. Again, as the particles congregate around the back of the ship the area gets white glow since the particles all blend together. This is due to the way we blend the particles, lets look at rendering the particle group. First take a look at the renderEngine() method in Player:
private void renderEngine() { // disable lighting for the particles, we want them to // be very glowy GL11.glDisable(GL11.GL_LIGHTING); // When rendering particles we want to blend them together // to give that smoothed look. Note that we're setting the // blend function to GL_ONE which causes the final alpha value // rendered to be ramped up making everything look glowing GL11.glEnable(GL11.GL_BLEND); GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE); // bind to our texture then ask the particle engine to render // all its particles shotTexture.bind(); engine.render(); // turn blending back off, since we're done now GL11.glDisable(GL11.GL_BLEND); }
Much like the bitmap font rendering we saw in the last part of this tutorial, heres where we handle the texture binding and the blending. However, this time we're binding to a texture that looks like a small star - which is painted across every particle quad we draw. Also, note the blending we're using here "GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE);" - this blending means thats we'll combine the alpha from the texture with the value GL_ONE (1). This means that the where the alpha value in the texture get high they'll end being drawn as a value of 1 - which in turn gives us that glowly look to the particles.
Ok, so we've set up the texture and blending function, here's the code in render() in ParticleGroup which actually produces the particle quads:
public void render() { GL11.glBegin(GL11.GL_QUADS); for (int i=0;i0) { float scalar = alpha[i] / 3; GL11.glColor4f((r * (1 - scalar)) + scalar, (g * (1 - scalar)) + scalar, (b * (1 - scalar)) + scalar, alpha[i]); GL11.glTexCoord2f(0,0); GL11.glVertex3f(pos[i][0]-size[i],pos[i][1]-size[i],-0.3f); GL11.glTexCoord2f(1,0); GL11.glVertex3f(pos[i][0]+size[i],pos[i][1]-size[i],-0.3f); GL11.glTexCoord2f(1,1); GL11.glVertex3f(pos[i][0]+size[i],pos[i][1]+size[i],-0.3f); GL11.glTexCoord2f(0,1); GL11.glVertex3f(pos[i][0]-size[i],pos[i][1]+size[i],-0.3f); } } GL11.glEnd(); GL11.glColor4f(1,1,1,1); }
The code above draws each of the particles as part of a OpenGL quad array. The size and position is read stright from the arrays we hold as part of the group (the z coordinate is constant since we want the quads to face the screen).
The colour of quad is based partly on the color requested by the ParticleGroup creator and partly on the remaining alpha of the particle (the value that goes down as the particle fades out). This alpha value is decremented as the particle gets older causing it to fade and dim as it dies. Here's how that happens in the update() method:
public void update(int delta) { // cycle through every particle, aging it. It the particle // is still alive this frame then fade it and shrink it for (int i=0;i= 0) { life[i] -= delta; if (life[i] < fadeOut) { alpha[i] = life[i] / ((float) fadeOut); size[i] = (life[i] / ((float) fadeOut)) * initialSize[i]; } } } }
When the particle group is updated the particles are cycled through. Each particle is aged based on the amount of time thats passed since the last update. This causes the particles to eventually shrink and fade as we've seen above. Note the "if (life[i] >= 0)" condition. If the particles have no life, they're ignored.
Cool! Now we've got a simple particle engine to fit our needs and its integrated with entities. This last touch makes the game a little more flash and adds a bit of a wow factor.
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 highlighted source is also available here:
Game.java
Entity.java
Map.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.
A tilemap is essentially an grid of cells where the value in the cell indicates what should be at that location. So we we might define "1" as grass and "2" as mud and define a map using these values describing an area in the game.
The first nice thing about tilemaps is they're simple. Its very easy to implement a tilemap using a standard two dimensional array. You simply fill it full of values and access them using x/y coordinates.
The second nice thing about tilemaps is that they often save space. You only define the properties of a particular tile type once. You then reuse that set of properties over and over by referencing it form the tilemap.
The last nice thing (that I can think of) about tilemaps is that they're quick to access. Given a particular location on the map it can be very fast to get to the properties associated with that area. Normally its simply a matter of accessing into an array based on the x/y location of an entity.
So, why are they are good for collision. Well, lets look at your standard maze style game. You've got a grid full of cells that are either blocked by a maze wall or free to move through. What are you going to do? Using more advanced techniques you might define a bounding area for each cell and then check your entity against every one! Yuck. Thats a lot of checking! Normally you'd do a broader check first, work out which cells to check first and hence do less bounding shape checks. Tilemaps can be used to perform this broader check - however, in many cases you don't need to perform the more complicated bonunds check at all :)
How does a tilemap help you? Well, instead of checking against every block, you can simply check against the properties for the blocks that are close to your entity. Of course, theres a trade off, its not as accurate as full bounds checking - but normally its more than enough. Lets look at this in more detail:

The diagram above shows a simply tile map with a couple of entities (red blobs). The blue cells represent blocked areas and the white cells clear ones. Around the entity we have a simple box collision bounds. The green points mark locations that we'll check for collision. So, the entity in the top left is in an invalid location (its partly into the wall). The entity is the middle is valid.
Now, how many points you check and where the points are specific to the application - and obviously this isn't perfect collision for everything. However, when entities are simply moving round a map at slow speeds even this 4 point checking can work fine (as we'll see in the tutorial code). Note that with this simple corner checking system the entity bounding box needs to be smaller than the cells of the grid. This is better in some cases because instead of checking resonably complicated bounds checks on bunch of cells, we check explicitly the ones we want to in a very quick tile map based way.
The tutorial code is built up of 3 classes. Lets just go over what they're for.
The game class is our central hub for everything. It handles window creation and the game loop. Its also responsible for creating the map and player.
The map is unsurprisingly our tile map. It contains a data array of values indicating whether each cell is blocked or not. The map is also responsible for rendering itself using simple rectangles.
The entity class represents things that are wandering the map colliding with the walls. In this example this is simply the player. The entity class is responsible for rendering a sprite representing itself.
Let's look at how the collision code works, starting in Game we see this:
public void logic(long delta) {
float dx = 0;
float dy = 0;
if (left) {
dx -= 1;
}
if (right) {
dx += 1;
}
if (up) {
dy -= 1;
}
if (down) {
dy += 1;
}
if ((dx != 0) || (dy != 0)) {
player.move(dx * delta * 0.003f,
dy * delta * 0.003f);
}
}
Elsewhere we've recorded which keys are pressed, we use this to work out which way the player wants their character to move. We then ask the player to move based on the direction and the amount of time thats passed. Player.move() looks like this:
public boolean move(float dx, float dy) { float nx = x + dx; float ny = y + dy; if (validLocation(nx, ny)) { x = nx; y = ny; return true; } return false; }
Doesn't do much does it? The actual work is being performed in validLocation(). This method simply works out where the entity will be if the move is performed. It checks if this is a valid location (which performs the collision checks). If it is a valid location it moves the player, if not it doesn't. This way you can't move the player into walls.
Right, so the important bit is in validLocation() which looks like this:
public boolean validLocation(float nx, float ny) {
if (map.blocked(nx - size, ny - size)) {
return false;
}
if (map.blocked(nx + size, ny - size)) {
return false;
}
if (map.blocked(nx - size, ny + size)) {
return false;
}
if (map.blocked(nx + size, ny + size)) {
return false;
}
return true;
}
Again, this is pretty simple right? We go away to the map and check the four points around our player that represent its collision box based on the "size" associated with this entity. If any of the points are blocked then this isn't a valid location - otherwise it is.
Now you can see how this might cause problems in certain cases but for simple situations its fast and most importantly simple. Finally, lets just finish off the picture by seeing what happens in the Map.blocked() method:
public boolean blocked(float x, float y) {
return data[(int) x][(int) y] == BLOCKED;
}
Really simple and really quick! We take the floating point values passed in (which are in grid cells) - cast them down to ints which instantly gives us the grid cell we need to check. Check the cell and return yes or no.
Thats it for code. The tutorial code does some other bits and pieces to create the window, capture key input and render the scene but this has all been covered in the space invaders tutorial.
Tilemaps make sense for certain situations. In most cases they're based combined with other techniques. For instance, it might make sense to use the tilemap to work out which cells need to be checked and then using bounds checking to work out the actual collision.
Tilemaps don't fit in every game but in many cases they are a powerful tool for both rendering and collision detection.
Implement Sliding - If you've tried the tutorial example you'll find that you don't slide along walls like you do in so many games. This is pretty easy to implement by just apply the x and y axis movement seperately.
Tile based Rendering - Implement a system to rendering a different sprite based on the value in the cells, giving a nice looking map
Tutorial written and coded by Kevin Glass
Java is availble from http://www.java.net
Path Finding is something that seems difficult at first thought. How do I work out the best way for one of the guys in my game to get from one location to another taking account of other things on the map. In the general case this is a pretty difficult problem, if you consider free form maps and accounting for other things wandering around the map at the same time it really gets very complicated.
This tutorial hopes to provide somewhere to start, explaining the most common path finding algorithm in the more simple case of tile based maps. It'll cover how the algorithm works and provide some reusable code to find paths across arbitrary tile based maps.
The full package for this tutorial can be downloaded here. The example provided can be tried via Java Web Start here.
The code for the tutorial is here:
Reusable Path Finding Code
PathFinder.java
AStarPathFinder.java
AStarHeuristic.java
Mover.java
Path.java
TileBasedMap.java
ClosestHeuristic.java
Example Game Prototype
PathTest.java
GameMap.java
UnitMover.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.
The A Star (A*) algorithm is probably one of the most commonly used in games programming. The reason is that, as we will see, it's extremely configurable to the particular type of game and map. It can be tuned to provide good performance against most data sets.
A* works by maintaining a pair of lists, one containing locations on the tile map which may be a next step in the path (called the 'open' list) and one containing locations that have already been searched (called the 'closed' list). The algorithm keeps looping while there are still next steps to be considered. It chooses the most likely of the next steps available and considers it's neighbors. Each neighbor gets added a potential next step and we continue looping. As each location is considered as a step it's removed from the open list and added to the closed list (i.e. the list of locations that have been searched). In this way we search the map for possible locations but tend towards choosing the "most likely" steps first - this is why the algorithm is reasonably efficient.
However, how do we determine which next step is "most likely"? Thats where the configuration of the algorithm comes in. Each time we need to choose the most likely next step we resolve each possible step against a "heuristic". This is just a formal name for a function that provides a priority value for anything (more on these later).
There are two primary values used in A*. The first, already mentioned, is the "heuristic cost". This cost determines which step is most likely. The second value we use to choose the best next step is the cost of getting to the current location, i.e. how many steps is the currently location away from starting point - this is called the "path cost". By combining these two values the algorithm considers both the most likely route and the shortest.
Let's look at the algorithm in more detail and in a more implementation oriented manner.
The A* algorithm works like this:
As mentioned already, the A* algorithm depends on evaluating the best next step in searching for a path. This is done by evaluating each of the possible next steps against a heuristic to give a value that can be used to sort the list and hence determine the most likely next step. As you can imagine this makes choosing a good heuristic for your map and game really important to getting good path finding performance.
In the example code we'll make the heuristic "pluggable", i.e. it'll be possible to provide a class to implement a new heuristic. Let's first look at a couple of commonly used heuristics.
The most obvious way to determine the best step is to always pick the step that is the closest to the target. This isn't always perfect for environments with a lot of obstacles or that are maze like but it does provide a simple heuristic to understand. It would look like:
dx = targetX - currentX;
dy = targetY - currentY;
heuristic = sqrt((dx*dx)+(dy*dy));
Remembering that the heuristic is evaluated frequently during the path finding process we can see that this may not always be a good idea. That sqrt() call is (on most hardware) expensive.
Another common approach is to replace absolute distance with "Manhattan Distance". This is an approximation of the distance between two points based on adding the horizontal distance and vertical distances rather than computing the exact difference. That would look like this:
dx = abs(targetX - currentX);
dy = abs(targetY - currentY);
heuristic = dx+dy;
This might not always work that well (especially if you allow diagonal movement) but it will work in some limited circumstances and is much cheaper. This is a good example of how the A* algorithm can be customized for specific games and maps.

To demonstrate A* we're going to build the beginnings of a game in which you can move units around on a game map. Different units will be able to move through different terrains and obstacles. The code will show a path from the selected unit to a destination before allowing us to actually move the unit itself.
The tutorial code used here is going to get committed into the Slick library as a utility, so ideally it should be nice and reusable. With this in mind we're going to build the code in such a way that different games can plug into it with the minimal of fuss.
In addition we need to make sure the heuristic can be changed for different games and that it's not too tricky to write a new heuristic specific to a given game. In the next couple of sections we'll look at the most important part of developing reusable code, the software contract.
The path finder code needs to be able to understand the game's concept of a map. We're taking the bold assumption that the game is 2D and grid based - though this covers a lot of different games. Looking at the algorithm, what does the path finder need to know about the game map:
public interface TileBasedMap {
public int getWidthInTiles();
public int getHeightInTiles();
public boolean blocked(Mover mover, int x, int y);
public float getCost(Mover mover, int sx, int sy, int tx, int ty);
public void pathFinderVisited(int x, int y);
}
There's also a second concept in the interface, the "Mover". In most games the thing that's actually going to move has properties that control where or what it can do - in our example for instance the boat can only move on water. If you look at the Mover.java interface you may be slightly surprised: It contains no methods at all! This type of interface is called a tagging or marker interface and just allows us to give context and typing to otherwise undefined entities. Essentially this means the Mover can be any object that the game knows about. As the object passes out of the game domain and into the reusable code domain it becomes an unknown object - an opaque Mover. When it re-enters the game domain (as it's passed into the TileBasedMap) the game implementation is safe to cast it back to whatever it passed in and make determinations based on it. There are slightly neater ways to do this using generics but it's thats out of scope here.
What the Mover interface gives us is the ability to make cost and blockage decisions based on the entity moving. A hero can't walk through walls - but maybe a ghost can. The Mover interface lets us make this distinction in the middle of our path finding algorithm.
Now we've seen what the game must provide, lets consider what path finding code gives us.
First we need to consider how the developer will invoke the path finder. They'll definitely have to provide a starting and ending point for the path each time. As mentioned above we'll also have to provide the description of the game entity doing the moving. Finally, the whole point, we'll want a path back from the invocation that describes the route from start to finish:
public interface PathFinder {
public Path findPath(Mover mover, int sx, int sy, int tx, int ty);
}
There's one thing missing, how does the path finder know what tile based map to be searching? In this case thats down to the implementation (which we'll see in a moment). Storing the map being searched in the PathFinder implementation has it's benefits - information about the map can be cached within the finder. However, this also means that the same path finder instance can't be reused over and over for different maps.
We've seen the Mover interface before, what about the Path object that gets returned. It contains just enough information to describe the route:
public class Path {
public int getLength();
public Step getStep(int index);
public int getX(int index);
public int getY(int index);
public void appendStep(int x, int y);
public void prependStep(int x, int y);
public boolean contains(int x, int y);
}
The path is essentially a list (in fact it's implemented with an ArrayList) of Step objects, where a "Step" represents a single location on the path. The methods should be self explanatory, we can simply add things to the path and then interrogate the list of steps when using it in the game.
We've now completed the contract between the game and the path finding code, yet we still can't actually do anything. We could write our game against this interface and check everything fits together nicely before implementing the A* algorithm. However, since thats the thing the tutorial is meant to be about we'll look at it first.
The A* algorithm as described above fits nicely into the contract we've defined. The PathFinder implementation of A* can be found in AStarPathFinder.java. We'll look at the algorithm implementation in detail next, but first let's consider how the algorithm elements have translated into the code.
The open and closed lists as described in the algorithm have become lists in our class:
/** The set of nodes that have been searched through */
private ArrayList closed = new ArrayList ();
/** The set of nodes that we do not yet consider fully searched */
private SortedList open = new SortedList ();
Notice how the open set is "sorted" meaning that our most likely next step can be sorted to the top of the set for selection. Next we have the map of data that is being searched:
/** The map being searched */
private TileBasedMap map;
..which is passed in at construction of the path finder. This allows us to cache this next bit, we need to store information about every cell in the tiled map:
/** The complete set of nodes across the map */
private Node[][] nodes;
So we've created a small inner class called "Node" which will hold information about particular locations on the map. This is quite an overhead but makes the code easy to read. It could be more optimal to store the information in primitive arrays, but lets not worry about that here. Finally we have the all important heuristic that drives the A*:
/** The heuristic we're applying to determine which nodes to search first */
private AStarHeuristic heuristic;
This is yet another interface, AStarHeuristic.java which we also (optionally) pass in at construction. This allows us to build custom heuristics for path finding. The heuristic interface has a single method:
public float getCost(TileBasedMap map, Mover mover, int x, int y, int tx, int ty);
Each time we need to determine the priority of a particular location we'll call this method, supplying the location we're considering and where we're trying to get to eventually. This should make writing new heuristics simple and nicely separate.
Now we know whats available to us, lets look at the A* implementation. Most of the work is done in the findPath() method which we'll cover in detail in the next few sections.
The initialization of the algorithm looks like this:
if (map.blocked(mover, tx, ty)) {
return null;
}
// initial state for A*. The closed group is empty. Only the starting
// tile is in the open list and it's cost is zero, i.e. we're already there
nodes[sx][sy].cost = 0;
nodes[sx][sy].depth = 0;
closed.clear();
open.clear();
open.add(nodes[sx][sy]);
// set the parent of the target location to null to mark that
// we haven't found a route yet
nodes[tx][ty].parent = null;
The first check we perform is an simple optimization. If the target we're attempting to find a route to is blocked then there is no way we can find a route there. Returning null indicates there's no path that can be found.
Next we initialize the information on the starting location and add it to the open list, ready to start searching. We're at initial state, the open set has one element and the closed set is empty.
This is the big bit, following along with the algorithm we need to loop choosing the best available step moving closer to the target and an appropriate path. Let's look at the code:
int maxDepth = 0;
while ((open.size() != 0)) && (maxDepth < maxSearchDistance)) {
So, were going to loop while there are still available steps and while the best next step isn't the target - i.e. until we've found the path or haven't got anywhere else to search. The extra piece here is a safe guard that most games will want, the search can't go on forever - we need to limit it. In this case we only allow the path search to get to certain number of steps in length before we give up.
Next we need to take the best step, mark is as searched and then consider it's neighbors:
Node current = getFirstInOpen();
if (current == nodes[tx][ty]) {
break;
}
removeFromOpen(current);
addToClosed(current);
for (int x=-1;x<2;x++) {
for (int y=-1;y<2;y++) {
if ((x == 0) && (y == 0)) {
continue;
}
int xp = x + current.x;
int yp = y + current.y;
if (isValidLocation(mover,sx,sy,xp,yp)) {
int nextStepCost = current.cost +
getMovementCost(mover, current.x,
current.y, xp, yp);
Node neighbour = nodes[xp][yp];
map.pathFinderVisited(xp, yp);
We grab the best next step (getFirstInOpen()) and consider this our "current" location. If the current location is the target, then end the search we've found our route! Removing the current location from the open list and adding it to the closed list marks it as searched. We then cycle through all of it's neighbors (excluding the [0,0] current location) considering their cost. Note that the path cost to the neighbor is calculated based on the cost to get the current node added to the movement cost associated with moving from current location to the neighbor.
For each of the neighbors we've found we need to consider two things. First, have we found a shorter path to this location than previously, if so we need to re-search it. Second, if we don't already have this as a step to consider we need to record it so it may get chosen as the next step and hence get us closer to the target:
if (nextStepCost < neighbour.cost) {
if (inOpenList(neighbour)) {
removeFromOpen(neighbour);
}
if (inClosedList(neighbour)) {
removeFromClosed(neighbour);
}
}
if (!inOpenList(neighbour) && !(inClosedList(neighbour))) {
neighbour.cost = nextStepCost;
maxDepth = Math.max(maxDepth, neighbour.setParent(current));
neighbour.heuristic = getHeuristicCost(mover, xp, yp, tx, ty);
open.add(neighbour);
}
This first condition up there is checking that we haven't found a better route to a node we'd previously considered searched (i.e. it's in the open or closed lists). If we've found a better route to the node (the cost is less than the recorded cost) then remove it from the lists it's stored in to mark as un-searched.
Finally we consider the neighbor in the context of whether its a good next step. If we haven't already searched it (or we've made it available again because we've found a better path) then we need to record the costs associated with it and make it a possible next step (add it to the open list). Note that we call setParent() on the node to record how we got to this new node. This information will be used later when we're determining the route we used to reached the target node and hence building the Path object to return.
Our final step once we've finished looping is to build a path object (if we found a path) and return it to the developer. That looks like this:
// since we've got an empty open list or we've run out of search
// there was no path. Just return null
if (nodes[tx][ty].parent == null) {
return null;
}
// At this point we've definitely found a path so we can uses the parent
// references of the nodes to find out way from the target location back
// to the start recording the nodes on the way.
Path path = new Path();
Node target = nodes[tx][ty];
while (target != nodes[sx][sy]) {
path.prependStep(target.x, target.y);
target = target.parent;
}
path.prependStep(sx,sy);
// thats it, we have our path
return path;
First, if we haven't found a route to the target we just return null to indicate that there was no path. If we did find a path we cycle through the information we've recorded stepping up through the parent's of the path to build up the route we've discovered. Pass it back to the user and we're done!
Now we've seen how to find the paths, let's quickly cover a simple example of using it.
The test case is built up of 3 classes. Most of the code is about rendering and handling mouse input which has been covered in other tutorials, so won't be covered in depth here. There are 3 classes:
PathTest.java - This is the main renderer, it draws a tile image for each location in the game map. It's also responsible for translating mouse movement and clicks into path finding operations.
GameMap.java - This is the game's implementation of TileBasedMap, it provides the description of each location in the game. Each location can hold up to two items of information, the type of terrain and optionally an indicator as to the type of unit. The interesting bit of code in terms of path finding is the blocked() method:
public boolean blocked(Mover mover, int x, int y) {
// if theres a unit at the location, then it's blocked
if (getUnit(x,y) != 0) {
return true;
}
int unit = ((UnitMover) mover).getType();
// planes can move anywhere
if (unit == PLANE) {
return false;
}
// tanks can only move across grass
if (unit == TANK) {
return terrain[x][y] != GRASS;
}
// boats can only move across water
if (unit == BOAT) {
return terrain[x][y] != WATER;
}
// unknown unit so everything blocks
return true;
}
So the implementation changes the valid locations based on the unit that is moving. It knows which unit is moving by looking at the Mover implementation passed in - which we'll see next. In this case tanks are limited to ground movement, boats are limited to water movement and planes can move anywhere.
UnitMover.java - Our mover implementation, it simply holds an integer indicating which type of unit is moving. In a full game implementation the mover interface may be implemented by the actual objects that represent game entities. However, for our purposes a simple integer is enough.
So, the PathTest class is responsible for rendering the map and accepting clicks. When the mouse moves it calls on a path finder to determine a path to the location the mouse is over. The path finder calls back on the GameMap it's been given using the UnitMover as the context in which the path is being found. With luck it finds a path, which it describes and passes back to the PathTest class. This path is then recorded and rendered next time the screen is updated.
It's all a little more complete than that, but it's left to the reader to fiddle with the example code to understand the details.
A* is a powerful and useful tool in game development. It's really not that complicated once you get going and it can be customized for optimal performance in different circumstances. It does get more complicated once you start considering other moving entities blocking the movement and the 3rd dimension, however hopefully this tutorial has given you a simple introduction that can be extended.
If you notice something that's wrong or could be better a different way please let me know at the address below.
Consider some more Heuristics - Search around the web, lots of people have implemented A* with different heuristics. Try some of them out.
Optimize the code - The code presented here is trying to be as simple as possible to aid understanding. Optimization options are plentiful.
Written By: Kevin Glass
Graphics from Advance Wars Net
Webstart is a SUN produced piece of software that uses the JNLP standard to support distribution and update of software. Basically this means that people can download your software from a website in an automated manner. Whats really nice is that they can also get updates when they run the software automatically!
This tutorial can be discussed here
Everything you distribute must be in Jar files. This include native libraries and resource files (models/maps/sprites etc..). Putting things in Jar files is pretty simple. The "jar" tool is distributed with the JDK. To put stuff in a jar you do this:
// putting myClassesDirectory in a jar file jar cf myjar.jar myClassesDirectory // putting gagetimer.dll in a jar file jar cf myjar.jar gagetimer.dll // putting the com directory which is inside the src directory in a jar file jar cf myjar.jar -C src com
The first jar that you distribute needs to be executable. This essentially means it has a file inside it called a "manifest" that tells java which class file it should run to execute the jar. The manifest file should look something like this:
Main-Class: org.newdawn.salvage.client.Game Class-Path: lib/net.jar lib/jogl.jar
The Main-Class element specifies the class that should be run when executing the Jar. The Class-Path element specifies the class path that should be used. Using WebStart this shouldn't matter since any Jar included in the download will be in the class path but it seemed worth mentioning.
To assign a manifest named "manifest.txt" to a jar file you do something like this:
// sticking a manifest in a jar file thats being created jar cfm myExecutableJar.jar manifest.txt -C src com // adding a manifest to an existing jar file jar ufm myExistingExecutableJar.jar manifest.txt
The final important thing to note about the Jars you distribute is that they must all be signed. This allows your jars to do normal stuff on the users machine assuming that they agree to allow you. Its a bit complicated so here's another section.
The first thing you're going to need to do is create a "key store". The key store is a file that contains your generated signing information. To create one you do something like this:
keytool -genkey -keystore myKeyStore -alias myName
The key tool is also part of the JDK. After typing the above you'll be asked lots of questions about who you are, this information will be provided to the end user when they're asked to accept your jars. You'll also be asked for a password for your name. You need this each time you sign anything. Talking of which, signing jars looks something like this:
jarsigner -keystore myKeyStore -storepass myPassword -keypass myNamePassword myJar.jar myName
This is pretty unwieldy and you have to remember lots of bits. Generally I stick this in a script and never think about it again. Remember you need to sign every jar you distribute.
Finally, we'll actually look at how to make something webstartable. First off you need to define a JNLP XML file to describe what you're going to distribute, heres an example:
<?xml version="1.0" encoding="utf-8"?>
<!-- Test for Astro Prime Web Start Deployment -->
<jnlp
spec="1.0+"
codebase="http://www.newdawnsoftware.com/astroprime/webstart"
href="astroprime.jnlp">
<information>
<title>AstroPrime MMO</title>
<vendor>Kev Glass - New Dawn Software</vendor>
<homepage href="http://www.newdawnsoftware.com"/>
<description>AstroPrime Client</description>
<description kind="short">MMO 2D Space Trading Game</description>
<icon href="icon.gif"/>
<icon kind="splash" href="splash.gif"/>
<offline-allowed/>
</information>
<security>
<all-permissions/>
</security>
<resources>
<j2se href="http://java.sun.com/products/autodl/j2se" version="1.4+"/>
<jar href="ap.jar"/>
<jar href="apresource.jar"/>
<jar href="lib/j2da.jar"/>
<jar href="lib/net.jar"/>
<jar href="lib/gagetimer.jar"/>
</resources>
<resources os="Windows">
<j2se href="http://java.sun.com/products/autodl/j2se" version="1.4+"/>
<jar href="lib/jogl-win32.jar"/>
<nativelib href="lib/jogl-win32-native.jar"/>
<nativelib href="lib/gagetimer-native.jar"/>
</resources>
<resources os="SunOS" arch="sparc">
<j2se href="http://java.sun.com/products/autodl/j2se" version="1.4+"/>
<jar href="lib/jogl-solsparc.jar"/>
<nativelib href="lib/jogl-solsparc-native.jar"/>
</resources>
<resources os="Linux">
<j2se href="http://java.sun.com/products/autodl/j2se" version="1.4+"/>
<jar href="lib/jogl-linux.jar"/>
<nativelib href="lib/jogl-linux-native.jar"/>
</resources>
<resources os="Mac OS">
<j2se href="http://java.sun.com/products/autodl/j2se" version="1.4+"/>
<jar href="lib/jogl-macos.jar"/>
<nativelib href="lib/jogl-macos-native.jar"/>
</resources>
<application-desc/>
</jnlp>
Its a pretty complicated example but I think it covers everything most people are going to want. I'll just nip through the tags:
JNLP tag simple describes the download area. "spec" should always be "1.0+". "codebase" is like the applet tag, its the base directory on the web server where everything is being downloaded from. "href" like in html is the reference to the actual JNLP file.
As you might have guessed this tag contains information about the produce. I think most of the tags are fairly self explanatory. All the information appears in one way or another in the WebStart GUI.
Resources describes all the jars that need to be downloaded and can also define the resources required on the target platform. The "os" tag specifies which operating system the resources are applicable to. If its not specified the resources are assumed to be required on all platforms. The "j2se" tag specifies a location where the java VM required can be downloaded. The "jar" tag specified a normal jar file that needs to be downloaded. The "nativelib" tag specifies a jar file that contains native libraries.
So finally you've signed all your jars, dumped all your files on a web server including your JNLP file. What else do you need to think about. JNLP only works if the correct MIME-type is returned by the web server. If you think you've got everything else right and clicking on your JNLP file on the webserver doesn't start WebStart, your web server probably doesn't support the MIME type.
The mime type needs to be "application/x-java-jnlp-file" for *.jnlp files. The easiest way to get this updated is to contact the admin for your webserver and have it updated. If you don't have this sort of access you can hack it to work using a bit of PHP or JSP.
If you're using Apache webserver you can put a .htaccess file in your public_html directory. If you add the following line, it'll add the mime-type for you.
AddType application/x-java-jnlp-file .jnlp
Rename the .jnlp file to .php (or any .php extension you are require to use). Add the following line at top of the file :
<?php header("Content-type: application/x-java-jnlp-file");
echo "<?xml version=\"1.0\" encoding=\"UTF-8\"?>";?>
Rename the .jnlp file as .jsp file, add the following line at top of the file :
<%@ page contentType="application/x-java-jnlp-file" %>
Interesting side note here. You put all your resources in a jar file right, so how can you access them from inside your program? If you've been accessing them directly by file then you've got alot of change. You can use the class loader to pick them up out of resources by using:
// Getting hold of a URL to sprite
Thread.currentThread().getContextClassLoader().getResource("sprites/mySprite.png");
// Getting hold of a stream to a sound
Thread.currentThread().getContextClassLoader().getResourceAsStream("sounds/bang.wav");
Now to those of you who normally access your resources via the class loader this might look a bit long winded. The important thing to realise is that when a game runs in WebStart its loaded into a "special" secure class loader. This means that you must use the same class loader to load your resources. Doing a "Thread.currentThread().getClassLoader()" ensures you get the class loader that webstart is using.
If you spot errors, think something needs changing or have problems let me know here.