diff --git a/src/java/org/lwjgl/examples/spaceinvaders/AlienEntity.java b/src/java/org/lwjgl/examples/spaceinvaders/AlienEntity.java
new file mode 100644
index 00000000..c266d6d4
--- /dev/null
+++ b/src/java/org/lwjgl/examples/spaceinvaders/AlienEntity.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright (c) 2002-2004 LWJGL Project
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'LWJGL' nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.lwjgl.examples.spaceinvaders;
+
+/**
+ * An entity which represents one of our space invader aliens.
+ *
+ * @author Kevin Glass
+ * @author Brian Matzon
+ */
+public class AlienEntity extends Entity {
+
+ /** Movement made downwards when a border is hit */
+ private static final int DOWNWARD_MOVEMENT = 10;
+
+ /** Border at which player dies */
+ private static final int BOTTOM_BORDER = 570;
+
+ /** Right border at which to shift direction */
+ private static final int RIGHT_BORDER = 750;
+
+ /** Left border at which to shift direction */
+ private static final int LEFT_BORDER = 10;
+
+ /** The speed at which the alient moves horizontally */
+ private float moveSpeed = 75;
+
+ /** The game in which the entity exists */
+ private Game game;
+
+ /** 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;
+
+ /**
+ * Create a new alien entity
+ *
+ * @param game The game in which this entity is being created
+ * @param x The intial x location of this alien
+ * @param y The intial y location of this alient
+ */
+ public AlienEntity(Game game, int x, int y) {
+ super(game.getSprite("alien.gif"), x, y);
+
+ // setup the animatin frames
+ frames[0] = sprite;
+ frames[1] = game.getSprite("alien2.gif");
+ frames[2] = sprite;
+ frames[3] = game.getSprite("alien3.gif");
+
+ this.game = game;
+ dx = -moveSpeed;
+ }
+
+ /**
+ * Request that this alien moved based on time elapsed
+ *
+ * @param delta The time that has elapsed since last move
+ */
+ 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];
+ }
+
+ // if we have reached the left hand side of the screen and
+ // are moving left then request a logic update
+ if ((dx < 0) && (x < LEFT_BORDER)) {
+ 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 > RIGHT_BORDER)) {
+ game.updateLogic();
+ }
+
+ // proceed with normal move
+ super.move(delta);
+ }
+
+ /**
+ * Update the game logic related to aliens
+ */
+ public void doLogic() {
+ // swap over horizontal movement and move down the
+ // screen a bit
+ dx = -dx;
+ y += DOWNWARD_MOVEMENT;
+
+ // if we've reached the bottom of the screen then the player
+ // dies
+ if (y > BOTTOM_BORDER) {
+ game.notifyDeath();
+ }
+ }
+
+ /**
+ * Notification that this alien has collided with another entity
+ *
+ * @param other The other entity
+ */
+ public void collidedWith(Entity other) {
+ // collisions with aliens are handled elsewhere
+ }
+}
\ No newline at end of file
diff --git a/src/java/org/lwjgl/examples/spaceinvaders/Entity.java b/src/java/org/lwjgl/examples/spaceinvaders/Entity.java
new file mode 100644
index 00000000..d189bf3d
--- /dev/null
+++ b/src/java/org/lwjgl/examples/spaceinvaders/Entity.java
@@ -0,0 +1,184 @@
+/*
+ * Copyright (c) 2002-2004 LWJGL Project
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'LWJGL' nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.lwjgl.examples.spaceinvaders;
+
+import java.awt.Rectangle;
+
+/**
+ * An entity represents any element that appears in the game. The
+ * entity is responsible for resolving collisions and movement
+ * based on a set of properties defined either by subclass or externally.
+ *
+ * Note that doubles are used for positions. This may seem strange
+ * given that pixels locations are integers. However, using double means
+ * that an entity can move a partial pixel. It doesn't of course mean that
+ * they will be display half way through a pixel but allows us not lose
+ * accuracy as we move.
+ *
+ * @author Kevin Glass
+ */
+public abstract class Entity {
+
+ /** The current x location of this entity */
+ protected float x;
+
+ /** The current y location of this entity */
+ protected float y;
+
+ /** The sprite that represents this entity */
+ protected Sprite sprite;
+
+ /** The current speed of this entity horizontally (pixels/sec) */
+ protected float dx;
+
+ /** The current speed of this entity vertically (pixels/sec) */
+ protected float dy;
+
+ /** The rectangle used for this entity during collisions resolution */
+ private Rectangle me = new Rectangle();
+
+ /** The rectangle used for other entities during collision resolution */
+ private Rectangle him = new Rectangle();
+
+ /**
+ * Construct a entity based on a sprite image and a location.
+ *
+ * @param ref The reference to the image to be displayed for this entity
+ * @param x The initial x location of this entity
+ * @param y The initial y location of this entity
+ */
+ public Entity(Sprite sprite, int x, int y) {
+ this.sprite = sprite;
+ this.x = x;
+ this.y = y;
+ }
+
+ /**
+ * Request that this entity move itself based on a certain ammount
+ * of time passing.
+ *
+ * @param delta The ammount of time that has passed in milliseconds
+ */
+ public void move(long delta) {
+ // update the location of the entity based on move speeds
+ x += (delta * dx) / 1000;
+ y += (delta * dy) / 1000;
+ }
+
+ /**
+ * Set the horizontal speed of this entity
+ *
+ * @param dx The horizontal speed of this entity (pixels/sec)
+ */
+ public void setHorizontalMovement(float dx) {
+ this.dx = dx;
+ }
+
+ /**
+ * Set the vertical speed of this entity
+ *
+ * @param dy The vertical speed of this entity (pixels/sec)
+ */
+ public void setVerticalMovement(float dy) {
+ this.dy = dy;
+ }
+
+ /**
+ * Get the horizontal speed of this entity
+ *
+ * @return The horizontal speed of this entity (pixels/sec)
+ */
+ public float getHorizontalMovement() {
+ return dx;
+ }
+
+ /**
+ * Get the vertical speed of this entity
+ *
+ * @return The vertical speed of this entity (pixels/sec)
+ */
+ public float getVerticalMovement() {
+ return dy;
+ }
+
+ /**
+ * Draw this entity to the graphics context provided
+ */
+ public void draw() {
+ sprite.draw((int) x, (int) y);
+ }
+
+ /**
+ * Do the logic associated with this entity. This method
+ * will be called periodically based on game events
+ */
+ public void doLogic() {
+ }
+
+ /**
+ * Get the x location of this entity
+ *
+ * @return The x location of this entity
+ */
+ public int getX() {
+ return (int) x;
+ }
+
+ /**
+ * Get the y location of this entity
+ *
+ * @return The y location of this entity
+ */
+ public int getY() {
+ return (int) y;
+ }
+
+ /**
+ * Check if this entity collised with another.
+ *
+ * @param other The other entity to check collision against
+ * @return True if the entities collide with each other
+ */
+ 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);
+ }
+
+ /**
+ * Notification that this entity collided with another.
+ *
+ * @param other The entity with which this entity collided.
+ */
+ public abstract void collidedWith(Entity other);
+}
\ No newline at end of file
diff --git a/src/java/org/lwjgl/examples/spaceinvaders/Game.java b/src/java/org/lwjgl/examples/spaceinvaders/Game.java
new file mode 100644
index 00000000..49335e0e
--- /dev/null
+++ b/src/java/org/lwjgl/examples/spaceinvaders/Game.java
@@ -0,0 +1,607 @@
+/*
+ * Copyright (c) 2002-2004 LWJGL Project
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'LWJGL' nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.lwjgl.examples.spaceinvaders;
+
+import java.util.ArrayList;
+
+import org.lwjgl.Display;
+import org.lwjgl.DisplayMode;
+import org.lwjgl.LWJGLException;
+import org.lwjgl.Sys;
+import org.lwjgl.input.Controller;
+import org.lwjgl.input.Keyboard;
+import org.lwjgl.input.Mouse;
+import org.lwjgl.opengl.GL11;
+import org.lwjgl.opengl.Window;
+
+/**
+ * The main hook of our game. This class with both act as a manager
+ * for the display and central mediator for the game logic.
+ *
+ * Display management will consist of a loop that cycles round all
+ * entities in the game asking them to move and then drawing them
+ * in the appropriate place. With the help of an inner class it
+ * will also allow the player to control the main ship.
+ *
+ * As a mediator it will be informed when entities within our game
+ * detect events (e.g. alient killed, played died) and will take
+ * appropriate game actions.
+ *
+ *
+ * NOTE:
+ * This game is a LWJGLized implementation of the Space Invaders game by Kevin
+ * Glass. The original implementation is renderer agnostic and supports other
+ * OpenGL implementations as well as Java2D. This version has been made specific
+ * for LWJGL, and has added input control as well as sound (which the original doesn't,
+ * at the time of writing).
+ * You can find the original article here:
+ * http://www.cokeandcode.com
+ *
+ *
+ * @author Kevin Glass
+ * @author Brian Matzon
+ */
+public class Game {
+
+ /** The normal title of the window */
+ private String WINDOW_TITLE = "Space Invaders 104 (for LWJGL)";
+
+ /** The width of the game display area */
+ private int width = 800;
+
+ /** The height of the game display area */
+ private int height = 600;
+
+ /** The loader responsible for converting images into OpenGL textures */
+ private TextureLoader textureLoader;
+
+ /** The list of all the entities that exist in our game */
+ private ArrayList entities = new ArrayList();
+
+ /** The list of entities that need to be removed from the game this loop */
+ private ArrayList removeList = new ArrayList();
+
+ /** The entity representing the player */
+ private ShipEntity ship;
+
+ /** List of shots */
+ private ShotEntity[] shots;
+
+ /** The message to display which waiting for a key press */
+ private Sprite message;
+
+ /** The sprite containing the "Press Any Key" message */
+ private Sprite pressAnyKey;
+
+ /** The sprite containing the "You win!" message */
+ private Sprite youWin;
+
+ /** The sprite containing the "You lose!" message */
+ private Sprite gotYou;
+
+ /** Last shot index */
+ private int shotIndex;
+
+ /** The speed at which the player's ship should move (pixels/sec) */
+ private float moveSpeed = 300;
+
+ /** The time at which last fired a shot */
+ private long lastFire = 0;
+
+ /** The interval between our players shot (ms) */
+ private long firingInterval = 500;
+
+ /** The number of aliens left on the screen */
+ private int alienCount;
+
+ /** True if we're holding up game play until a key has been pressed */
+ private boolean waitingForKeyPress = true;
+
+ /** True if game logic needs to be applied this loop, normally as a result of a game event */
+ private boolean logicRequiredThisLoop = false;
+
+ /** The time at which the last rendering looped started from the point of view of the game logic */
+ private long lastLoopTime = getTime();
+
+ /** True if the fire key has been released */
+ private boolean fireHasBeenReleased = false;
+
+ /** The time since the last record of fps */
+ private long lastFpsTime = 0;
+
+ /** The recorded fps */
+ private int fps;
+
+ private static long timerTicksPerSecond = Sys.getTimerResolution();
+
+ /** True if the game is currently "running", i.e. the game loop is looping */
+ public static boolean gameRunning = true;
+
+ /** SoundManager to make sound with */
+ private SoundManager soundManager;
+
+ /** Whether we're running in fullscreen mode */
+ private boolean fullscreen;
+
+ /** ID of shot effect */
+ private int SOUND_SHOT;
+
+ /** ID of hit effect */
+ private int SOUND_HIT;
+
+ /** ID of start sound */
+ private int SOUND_START;
+
+ /** ID of win sound */
+ private int SOUND_WIN;
+
+ /** ID of loose sound */
+ private int SOUND_LOOSE;
+
+ /** Mouse movement on x axis */
+ private int mouseX;
+
+ /**
+ * Construct our game and set it running.
+ * @param fullscreen
+ *
+ * @param renderingType The type of rendering to use (should be one of the contansts from ResourceFactory)
+ */
+ public Game(boolean fullscreen) {
+ this.fullscreen = fullscreen;
+ initialize();
+ }
+
+ /**
+ * 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 (Sys.getTime() * 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) {
+ try {
+ Thread.sleep((duration * timerTicksPerSecond) / 1000);
+ } catch (InterruptedException inte) {
+ }
+ }
+
+ /**
+ * Intialise the common elements for the game
+ */
+ public void initialize() {
+ // initialize the window beforehand
+ try {
+ if (fullscreen && setDisplayMode()) {
+ Window.create(WINDOW_TITLE, Display.getDepth(), 0, 8, 0, 0);
+ } else {
+ Window.create(WINDOW_TITLE, 100, 100, width, height, Display.getDepth(), 0, 8, 0, 0);
+ }
+
+ // 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();
+
+ // create our sound manager, and initialize it with 7 channels
+ // 1 channel for sounds, 6 for effects - this should be enough
+ // since we have a most 4 shots on screen at any one time, which leaves
+ // us with 2 channels for explosions.
+ soundManager = new SoundManager();
+ soundManager.initialize(8);
+
+ // load our sound data
+ SOUND_SHOT = soundManager.addSound("shot.wav");
+ SOUND_HIT = soundManager.addSound("hit.wav");
+ SOUND_START = soundManager.addSound("start.wav");
+ SOUND_WIN = soundManager.addSound("win.wav");
+ SOUND_LOOSE = soundManager.addSound("loose.wav");
+ } catch (LWJGLException le) {
+ System.out.println("Game exiting - exception in initialization:");
+ le.printStackTrace();
+ Game.gameRunning = false;
+ return;
+ }
+
+ // get our sprites
+ gotYou = getSprite("gotyou.gif");
+ pressAnyKey = getSprite("pressanykey.gif");
+ youWin = getSprite("youwin.gif");
+
+ message = pressAnyKey;
+
+ // setup 5 shots
+ shots = new ShotEntity[5];
+ for (int i = 0; i < shots.length; i++) {
+ shots[i] = new ShotEntity(this, "shot.gif", 0, 0);
+ }
+
+ // setup the initial game state
+ startGame();
+ }
+
+ /**
+ * Sets the display mode for fullscreen mode
+ */
+ private boolean setDisplayMode() {
+ // get modes
+ DisplayMode[] dm = Display.getAvailableDisplayModes();
+
+ // locate the first one that has 800*600*32
+ for (int i = 0; i < dm.length; i++) {
+ if (dm[i].width == 800 && dm[i].height == 600 && dm[i].bpp == 32) {
+ try {
+ Display.setDisplayMode(dm[i]);
+ return true;
+ } catch (LWJGLException le) {
+ le.printStackTrace();
+ System.out.println("Unable to enter fullscreen, continuing in windowed mode");
+ break;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Start a fresh game, this should clear out any old data and
+ * create a new set.
+ */
+ private void startGame() {
+ // clear out any existing entities and intialise a new set
+ entities.clear();
+ initEntities();
+ }
+
+ /**
+ * Initialise the starting state of the entities (ship and aliens). Each
+ * entitiy will be added to the overall list of entities in the game.
+ */
+ private void initEntities() {
+ // create the player ship and place it roughly in the center of the screen
+ ship = new ShipEntity(this, "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, 100 + (x * 50), (50) + row * 30);
+ entities.add(alien);
+ alienCount++;
+ }
+ }
+ }
+
+ /**
+ * Notification from a game entity that the logic of the game
+ * should be run at the next opportunity (normally as a result of some
+ * game event)
+ */
+ public void updateLogic() {
+ logicRequiredThisLoop = true;
+ }
+
+ /**
+ * Remove an entity from the game. The entity removed will
+ * no longer move or be drawn.
+ *
+ * @param entity The entity that should be removed
+ */
+ public void removeEntity(Entity entity) {
+ removeList.add(entity);
+ }
+
+ /**
+ * Notification that the player has died.
+ */
+ public void notifyDeath() {
+ if (!waitingForKeyPress) {
+ soundManager.playSound(SOUND_LOOSE);
+ }
+ message = gotYou;
+ waitingForKeyPress = true;
+ }
+
+ /**
+ * Notification that the player has won since all the aliens
+ * are dead.
+ */
+ public void notifyWin() {
+ message = youWin;
+ waitingForKeyPress = true;
+ soundManager.playSound(SOUND_WIN);
+ }
+
+ /**
+ * Notification that an alien has been killed
+ */
+ 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.02f);
+ }
+ }
+
+ soundManager.playEffect(SOUND_HIT);
+ }
+
+ /**
+ * Attempt to fire a shot from the player. Its called "try"
+ * since we must first check that the player can fire at this
+ * point, i.e. has he/she waited long enough between shots
+ */
+ 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 = shots[shotIndex++ % shots.length];
+ shot.reinitialize(ship.getX() + 10, ship.getY() - 30);
+ entities.add(shot);
+
+ soundManager.playEffect(SOUND_SHOT);
+ }
+
+ /**
+ * Run the main game loop. This method keeps rendering the scene
+ * and requesting that the callback update its screen.
+ */
+ private void gameLoop() {
+ while (Game.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
+ frameRendering();
+
+ // update window contents
+ Window.update();
+ }
+ }
+
+ /**
+ * Notification that a frame is being rendered. Responsible for
+ * running game logic and rendering the scene.
+ */
+ public void frameRendering() {
+ //SystemTimer.sleep(lastLoopTime+10-SystemTimer.getTime());
+ Display.sync(60);
+
+ // 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 = getTime() - lastLoopTime;
+ lastLoopTime = getTime();
+ lastFpsTime += delta;
+ fps++;
+
+ // update our FPS counter if a second has passed
+ if (lastFpsTime >= 1000) {
+ Window.setTitle(WINDOW_TITLE + " (FPS: " + fps + ")");
+ lastFpsTime = 0;
+ fps = 0;
+ }
+
+ // cycle round asking each entity to move itself
+ if (!waitingForKeyPress && !soundManager.isPlayingSound()) {
+ 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();
+ }
+
+ // 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);
+ }
+ }
+ }
+
+ // remove any entity that has been marked for clear up
+ entities.removeAll(removeList);
+ removeList.clear();
+
+ // 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 we're waiting for an "any key" press then draw the
+ // current message
+ if (waitingForKeyPress) {
+ message.draw(325, 250);
+ }
+
+ // resolve the movemfent of the ship. First assume the ship
+ // isn't moving. If either cursor key is pressed then
+ // update the movement appropraitely
+ ship.setHorizontalMovement(0);
+
+ // get mouse movement on x axis. We need to get it now, since
+ // we can only call getDX ONCE! - secondary calls will yield 0, since
+ // there haven't been any movement since last call.
+ mouseX = Mouse.getDX();
+
+ // we delegate input checking to submethod since we want to check
+ // for keyboard, mouse & controller
+ boolean leftPressed = hasInput(Keyboard.KEY_LEFT);
+ boolean rightPressed = hasInput(Keyboard.KEY_RIGHT);
+ boolean firePressed = hasInput(Keyboard.KEY_SPACE);
+
+ if (!waitingForKeyPress && !soundManager.isPlayingSound()) {
+ if ((leftPressed) && (!rightPressed)) {
+ ship.setHorizontalMovement(-moveSpeed);
+ } else if ((rightPressed) && (!leftPressed)) {
+ ship.setHorizontalMovement(moveSpeed);
+ }
+
+ // if we're pressing fire, attempt to fire
+ if (firePressed) {
+ tryToFire();
+ }
+ } else {
+ if (!firePressed) {
+ fireHasBeenReleased = true;
+ }
+ if ((firePressed) && (fireHasBeenReleased) && !soundManager.isPlayingSound()) {
+ waitingForKeyPress = false;
+ fireHasBeenReleased = false;
+ startGame();
+ soundManager.playSound(SOUND_START);
+ }
+ }
+
+ // if escape has been pressed, stop the game
+ if (Window.isCloseRequested() || Keyboard.isKeyDown(Keyboard.KEY_ESCAPE)) {
+ Game.gameRunning = false;
+ }
+ }
+
+ /**
+ * @param key_left
+ * @return
+ */
+ private boolean hasInput(int direction) {
+ switch(direction) {
+ case Keyboard.KEY_LEFT:
+ return
+ Keyboard.isKeyDown(Keyboard.KEY_LEFT) ||
+ mouseX < 0 ||
+ (Controller.isCreated() && Controller.getX() < 0);
+
+ case Keyboard.KEY_RIGHT:
+ return
+ Keyboard.isKeyDown(Keyboard.KEY_RIGHT) ||
+ mouseX > 0 ||
+ (Controller.isCreated() && Controller.getX() > 0);
+
+ case Keyboard.KEY_SPACE:
+ return
+ Keyboard.isKeyDown(Keyboard.KEY_SPACE) ||
+ Mouse.isButtonDown(0) ||
+ (Controller.isCreated() && Controller.isButtonDown(0));
+ }
+ return false;
+ }
+
+ /**
+ * The entry point into the game. We'll simply create an
+ * instance of class which will start the display and game
+ * loop.
+ *
+ * @param argv The arguments that are passed into our game
+ */
+ public static void main(String argv[]) {
+ new Game((argv.length > 0 && argv[0].equalsIgnoreCase("-fullscreen"))).execute();
+ }
+
+ /**
+ *
+ */
+ private void execute() {
+ gameLoop();
+ soundManager.destroy();
+ Window.destroy();
+ }
+
+ /**
+ * Create or get a sprite which displays the image that is pointed
+ * to in the classpath by "ref"
+ *
+ * @param ref A reference to the image to load
+ * @return A sprite that can be drawn onto the current graphics context.
+ */
+ public Sprite getSprite(String ref) {
+ return new Sprite(textureLoader, ref);
+ }
+}
\ No newline at end of file
diff --git a/src/java/org/lwjgl/examples/spaceinvaders/ShipEntity.java b/src/java/org/lwjgl/examples/spaceinvaders/ShipEntity.java
new file mode 100644
index 00000000..2a76d5c5
--- /dev/null
+++ b/src/java/org/lwjgl/examples/spaceinvaders/ShipEntity.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (c) 2002-2004 LWJGL Project
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'LWJGL' nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.lwjgl.examples.spaceinvaders;
+
+/**
+ * The entity that represents the players ship
+ *
+ * @author Kevin Glass
+ * @author Brian Matzon
+ */
+public class ShipEntity extends Entity {
+
+ /** Right border at which to disallow further movement */
+ private static final int RIGHT_BORDER = 750;
+
+ /** Left border at which to disallow further movement */
+ private static final int LEFT_BORDER = 10;
+
+ /** The game in which the ship exists */
+ private Game game;
+
+ /**
+ * Create a new entity to represent the players ship
+ *
+ * @param game The game in which the ship is being created
+ * @param ref The reference to the sprite to show for the ship
+ * @param x The initial x location of the player's ship
+ * @param y The initial y location of the player's ship
+ */
+ public ShipEntity(Game game,String ref,int x,int y) {
+ super(game.getSprite(ref), x, y);
+
+ this.game = game;
+ }
+
+ /**
+ * Request that the ship move itself based on an elapsed ammount of
+ * time
+ *
+ * @param delta The time that has elapsed since last move (ms)
+ */
+ 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 < LEFT_BORDER)) {
+ return;
+ }
+ // if we're moving right and have reached the right hand side
+ // of the screen, don't move
+ if ((dx > 0) && (x > RIGHT_BORDER)) {
+ return;
+ }
+
+ super.move(delta);
+ }
+
+ /**
+ * Notification that the player's ship has collided with something
+ *
+ * @param other The entity with which the ship has collided
+ */
+ public void collidedWith(Entity other) {
+ // if its an alien, notify the game that the player
+ // is dead
+ if (other instanceof AlienEntity) {
+ game.notifyDeath();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/java/org/lwjgl/examples/spaceinvaders/ShotEntity.java b/src/java/org/lwjgl/examples/spaceinvaders/ShotEntity.java
new file mode 100644
index 00000000..2e953a4d
--- /dev/null
+++ b/src/java/org/lwjgl/examples/spaceinvaders/ShotEntity.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (c) 2002-2004 LWJGL Project
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'LWJGL' nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.lwjgl.examples.spaceinvaders;
+
+/**
+ * An entity representing a shot fired by the player's ship
+ *
+ * @author Kevin Glass
+ * @author Brian Matzon
+ */
+public class ShotEntity extends Entity {
+
+ /** Top border at which shots are outside */
+ private static final int TOP_BORDER = -100;
+
+ /** The vertical speed at which the players shot moves */
+ private float moveSpeed = -300;
+
+ /** The game in which this entity exists */
+ private Game game;
+
+ /** True if this shot has been "used", i.e. its hit something */
+ private boolean used = false;
+
+ /**
+ * Create a new shot from the player
+ *
+ * @param game The game in which the shot has been created
+ * @param sprite The sprite representing this shot
+ * @param x The initial x location of the shot
+ * @param y The initial y location of the shot
+ */
+ public ShotEntity(Game game, String sprite, int x, int y) {
+ super(game.getSprite(sprite), x, y);
+
+ this.game = game;
+ dy = moveSpeed;
+ }
+
+ /**
+ * Reinitializes this entity, for reuse
+ *
+ * @param x new x coordinate
+ * @param y new y coordinate
+ */
+ public void reinitialize(int x, int y) {
+ this.x = x;
+ this.y = y;
+ used = false;
+ }
+
+ /**
+ * Request that this shot moved based on time elapsed
+ *
+ * @param delta The time that has elapsed since last move
+ */
+ public void move(long delta) {
+ // proceed with normal move
+ super.move(delta);
+
+ // if we shot off the screen, remove ourselfs
+ if (y < TOP_BORDER) {
+ game.removeEntity(this);
+ }
+ }
+
+ /**
+ * Notification that this shot has collided with another
+ * entity
+ *
+ * @param other The other entity with which we've collided
+ */
+ public void collidedWith(Entity other) {
+ // prevents double kills, if we've already hit something,
+ // don't collide
+ if (used) {
+ return;
+ }
+
+ // 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();
+ used = true;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/java/org/lwjgl/examples/spaceinvaders/SoundManager.java b/src/java/org/lwjgl/examples/spaceinvaders/SoundManager.java
new file mode 100644
index 00000000..437ab718
--- /dev/null
+++ b/src/java/org/lwjgl/examples/spaceinvaders/SoundManager.java
@@ -0,0 +1,189 @@
+/*
+ * Copyright (c) 2002-2004 LWJGL Project
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'LWJGL' nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.lwjgl.examples.spaceinvaders;
+
+import java.nio.IntBuffer;
+
+import org.lwjgl.BufferUtils;
+import org.lwjgl.LWJGLException;
+import org.lwjgl.openal.AL;
+import org.lwjgl.openal.AL10;
+import org.lwjgl.test.openal.WaveData;
+
+
+/**
+ * $Id$
+ *
+ * Simple sound manager for OpenAL using n sources accessed in
+ * a round robin schedule. Source n is reserved for a single buffer and checking for
+ * whether it's playing.
+ *
+ * @author Brian Matzon
+ * @version $Revision$
+ */
+public class SoundManager {
+
+ /** We support at most 256 buffers*/
+ private int[] buffers = new int[256];
+
+ /** Number of sources is limited tby user (and hardware) */
+ private int[] sources;
+
+ /** Our internal scratch buffer */
+ private IntBuffer scratchBuffer = BufferUtils.createIntBuffer(256);
+
+ /** Whether we're running in no sound mode */
+ private boolean soundOutput;
+
+ /** Current index in our buffers */
+ private int bufferIndex;
+
+ /** Current index in our source list */
+ private int sourceIndex;
+
+ /**
+ * Creates a new SoundManager
+ */
+ public SoundManager() {
+ }
+
+ /**
+ * Plays a sound effect
+ * @param buffer Buffer index to play gotten from addSound
+ */
+ public void playEffect(int buffer) {
+ if(soundOutput) {
+ // make sure we never choose last channel, since it is used for special sounds
+ int channel = sources[(sourceIndex++ % (sources.length-1))];
+
+ // link buffer and source, and play it
+ AL10.alSourcei(channel, AL10.AL_BUFFER, buffers[buffer]);
+ AL10.alSourcePlay(channel);
+ }
+ }
+
+ /**
+ * Plays a sound on last source
+ * @param buffer Buffer index to play gotten from addSound
+ */
+ public void playSound(int buffer) {
+ if(soundOutput) {
+ AL10.alSourcei(sources[sources.length-1], AL10.AL_BUFFER, buffers[buffer]);
+ AL10.alSourcePlay(sources[sources.length-1]);
+ }
+ }
+
+ /**
+ * Whether a sound is playing on last source
+ * @return true if a source is playing right now on source n
+ */
+ public boolean isPlayingSound() {
+ return AL10.alGetSourcei(sources[sources.length-1], AL10.AL_SOURCE_STATE) == AL10.AL_PLAYING;
+ }
+
+ /**
+ * Initializes the SoundManager
+ *
+ * @param sources Number of sources to create
+ */
+ public void initialize(int channels) {
+ try {
+ AL.create();
+
+ // allocate sources
+ scratchBuffer.limit(channels);
+ AL10.alGenSources(scratchBuffer);
+ scratchBuffer.rewind();
+ scratchBuffer.get(sources = new int[channels]);
+
+ // could we allocate all channels?
+ if(AL10.alGetError() != AL10.AL_NO_ERROR) {
+ throw new LWJGLException("Unable to allocate " + channels + " sources");
+ }
+
+ // we have sound
+ soundOutput = true;
+ } catch (LWJGLException le) {
+ le.printStackTrace();
+ System.out.println("Sound disabled");
+ }
+ }
+
+ /**
+ * Adds a sound to the Sound Managers pool
+ *
+ * @param path Path to file to load
+ * @return index into SoundManagers buffer list
+ */
+ public int addSound(String path) {
+ // Generate 1 buffer entry
+ scratchBuffer.rewind().position(0).limit(1);
+ AL10.alGenBuffers(scratchBuffer);
+ buffers[bufferIndex] = scratchBuffer.get(0);
+
+ // load wave data from buffer
+ WaveData wavefile = WaveData.create("spaceinvaders/" + path);
+
+ // copy to buffers
+ AL10.alBufferData(buffers[bufferIndex], wavefile.format, wavefile.data, wavefile.samplerate);
+
+ // unload file again
+ wavefile.dispose();
+
+ // return index for this sound
+ return bufferIndex++;
+ }
+
+ /**
+ * Destroy this SoundManager
+ */
+ public void destroy() {
+ if(soundOutput) {
+
+ // stop playing sounds
+ scratchBuffer.position(0).limit(sources.length);
+ scratchBuffer.put(sources).flip();
+ AL10.alSourceStop(scratchBuffer);
+
+ // destroy sources
+ AL10.alDeleteSources(scratchBuffer);
+
+ // destroy buffers
+ scratchBuffer.position(0).limit(bufferIndex);
+ scratchBuffer.put(buffers, 0, bufferIndex).flip();
+ AL10.alDeleteBuffers(scratchBuffer);
+
+ // destory OpenAL
+ AL.destroy();
+ }
+ }
+}
diff --git a/src/java/org/lwjgl/examples/spaceinvaders/Sprite.java b/src/java/org/lwjgl/examples/spaceinvaders/Sprite.java
new file mode 100644
index 00000000..626e4323
--- /dev/null
+++ b/src/java/org/lwjgl/examples/spaceinvaders/Sprite.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (c) 2002-2004 LWJGL Project
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'LWJGL' nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.lwjgl.examples.spaceinvaders;
+
+import java.io.IOException;
+
+import org.lwjgl.opengl.GL11;
+
+/**
+ * Implementation of sprite that uses an OpenGL quad and a texture
+ * to render a given image to the screen.
+ *
+ * @author Kevin Glass
+ * @author Brian Matzon
+ */
+public class Sprite {
+
+ /** The texture that stores the image for this sprite */
+ private Texture texture;
+
+ /** The width in pixels of this sprite */
+ private int width;
+
+ /** The height in pixels of this sprite */
+ private int height;
+
+ /**
+ * Create a new sprite from a specified image.
+ *
+ * @param window The window in which the sprite will be displayed
+ * @param ref A reference to the image on which this sprite should be based
+ */
+ public Sprite(TextureLoader loader, String ref) {
+ try {
+ texture = loader.getTexture("spaceinvaders/" + ref);
+ width = texture.getImageWidth();
+ height = texture.getImageHeight();
+ } catch (IOException ioe) {
+ ioe.printStackTrace();
+ System.exit(-1);
+ }
+ }
+
+ /**
+ * Get the width of this sprite in pixels
+ *
+ * @return The width of this sprite in pixels
+ */
+ public int getWidth() {
+ return texture.getImageWidth();
+ }
+
+ /**
+ * Get the height of this sprite in pixels
+ *
+ * @return The height of this sprite in pixels
+ */
+ public int getHeight() {
+ return texture.getImageHeight();
+ }
+
+ /**
+ * Draw the sprite at the specified location
+ *
+ * @param x The x location at which to draw this sprite
+ * @param y The y location at which to draw this sprite
+ */
+ 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);
+
+ // 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();
+ }
+}
\ No newline at end of file
diff --git a/src/java/org/lwjgl/examples/spaceinvaders/Texture.java b/src/java/org/lwjgl/examples/spaceinvaders/Texture.java
new file mode 100644
index 00000000..f3bf1baf
--- /dev/null
+++ b/src/java/org/lwjgl/examples/spaceinvaders/Texture.java
@@ -0,0 +1,190 @@
+/*
+ * Copyright (c) 2002-2004 LWJGL Project
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'LWJGL' nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.lwjgl.examples.spaceinvaders;
+
+import org.lwjgl.opengl.GL11;
+
+/**
+ * A texture to be bound within OpenGL. This object is responsible for
+ * keeping track of a given OpenGL texture and for calculating the
+ * texturing mapping coordinates of the full image.
+ *
+ * Since textures need to be powers of 2 the actual texture may be
+ * considerably bigged that the source image and hence the texture
+ * mapping coordinates need to be adjusted to matchup drawing the
+ * sprite against the texture.
+ *
+ * @author Kevin Glass
+ * @author Brian Matzon
+ */
+public class Texture {
+
+ /** The GL target type */
+ private int target;
+
+ /** The GL texture ID */
+ private int textureID;
+
+ /** The height of the image */
+ private int height;
+
+ /** The width of the image */
+ private int width;
+
+ /** The width of the texture */
+ private int texWidth;
+
+ /** The height of the texture */
+ private int texHeight;
+
+ /** The ratio of the width of the image to the texture */
+ private float widthRatio;
+
+ /** The ratio of the height of the image to the texture */
+ private float heightRatio;
+
+ /**
+ * Create a new texture
+ *
+ * @param target The GL target
+ * @param textureID The GL texture ID
+ */
+ public Texture(int target, int textureID) {
+ this.target = target;
+ this.textureID = textureID;
+ }
+
+ /**
+ * Bind the specified GL context to a texture
+ *
+ * @param gl The GL context to bind to
+ */
+ public void bind() {
+ GL11.glBindTexture(target, textureID);
+ }
+
+ /**
+ * Set the height of the image
+ *
+ * @param height The height of the image
+ */
+ public void setHeight(int height) {
+ this.height = height;
+ setHeight();
+ }
+
+ /**
+ * Set the width of the image
+ *
+ * @param width The width of the image
+ */
+ public void setWidth(int width) {
+ this.width = width;
+ setWidth();
+ }
+
+ /**
+ * Get the height of the original image
+ *
+ * @return The height of the original image
+ */
+ public int getImageHeight() {
+ return height;
+ }
+
+ /**
+ * Get the width of the original image
+ *
+ * @return The width of the original image
+ */
+ public int getImageWidth() {
+ return width;
+ }
+
+ /**
+ * Get the height of the physical texture
+ *
+ * @return The height of physical texture
+ */
+ public float getHeight() {
+ return heightRatio;
+ }
+
+ /**
+ * Get the width of the physical texture
+ *
+ * @return The width of physical texture
+ */
+ public float getWidth() {
+ return widthRatio;
+ }
+
+ /**
+ * Set the height of this texture
+ *
+ * @param texHeight The height of the texture
+ */
+ public void setTextureHeight(int texHeight) {
+ this.texHeight = texHeight;
+ setHeight();
+ }
+
+ /**
+ * Set the width of this texture
+ *
+ * @param texWidth The width of the texture
+ */
+ public void setTextureWidth(int texWidth) {
+ this.texWidth = texWidth;
+ setWidth();
+ }
+
+ /**
+ * Set the height of the texture. This will update the
+ * ratio also.
+ */
+ private void setHeight() {
+ if (texHeight != 0) {
+ heightRatio = ((float) height) / texHeight;
+ }
+ }
+
+ /**
+ * Set the width of the texture. This will update the
+ * ratio also.
+ */
+ private void setWidth() {
+ if (texWidth != 0) {
+ widthRatio = ((float) width) / texWidth;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/java/org/lwjgl/examples/spaceinvaders/TextureLoader.java b/src/java/org/lwjgl/examples/spaceinvaders/TextureLoader.java
new file mode 100644
index 00000000..ba7def4c
--- /dev/null
+++ b/src/java/org/lwjgl/examples/spaceinvaders/TextureLoader.java
@@ -0,0 +1,285 @@
+/*
+ * Copyright (c) 2002-2004 LWJGL Project
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'LWJGL' nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.lwjgl.examples.spaceinvaders;
+
+import java.awt.Color;
+import java.awt.Graphics;
+import java.awt.color.ColorSpace;
+import java.awt.image.BufferedImage;
+import java.awt.image.ColorModel;
+import java.awt.image.ComponentColorModel;
+import java.awt.image.DataBuffer;
+import java.awt.image.DataBufferByte;
+import java.awt.image.Raster;
+import java.awt.image.WritableRaster;
+import java.io.BufferedInputStream;
+import java.io.IOException;
+import java.net.URL;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.IntBuffer;
+import java.util.HashMap;
+import java.util.Hashtable;
+
+import javax.imageio.ImageIO;
+
+import org.lwjgl.BufferUtils;
+import org.lwjgl.opengl.GL11;
+
+/**
+ * A utility class to load textures for OpenGL. This source is based
+ * on a texture that can be found in the Java Gaming (www.javagaming.org)
+ * Wiki. It has been simplified slightly for explicit 2D graphics use.
+ *
+ * OpenGL uses a particular image format. Since the images that are
+ * loaded from disk may not match this format this loader introduces
+ * a intermediate image which the source image is copied into. In turn,
+ * this image is used as source for the OpenGL texture.
+ *
+ * @author Kevin Glass
+ * @author Brian Matzon
+ */
+public class TextureLoader {
+ /** The table of textures that have been loaded in this loader */
+ private HashMap table = new HashMap();
+
+ /** The colour model including alpha for the GL image */
+ private ColorModel glAlphaColorModel;
+
+ /** The colour model for the GL image */
+ private ColorModel glColorModel;
+
+ /** Scratch buffer for texture ID's */
+ private IntBuffer textureIDBuffer = BufferUtils.createIntBuffer(1);
+
+ /**
+ * Create a new texture loader based on the game panel
+ *
+ * @param gl The GL content in which the textures should be loaded
+ */
+ public TextureLoader() {
+ glAlphaColorModel = new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_sRGB),
+ new int[] {8,8,8,8},
+ true,
+ false,
+ ComponentColorModel.TRANSLUCENT,
+ DataBuffer.TYPE_BYTE);
+
+ glColorModel = new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_sRGB),
+ new int[] {8,8,8,0},
+ false,
+ false,
+ ComponentColorModel.OPAQUE,
+ DataBuffer.TYPE_BYTE);
+ }
+
+ /**
+ * Create a new texture ID
+ *
+ * @return A new texture ID
+ */
+ private int createTextureID() {
+ GL11.glGenTextures(textureIDBuffer);
+ return textureIDBuffer.get(0);
+ }
+
+ /**
+ * Load a texture
+ *
+ * @param resourceName The location of the resource to load
+ * @return The loaded texture
+ * @throws IOException Indicates a failure to access the resource
+ */
+ public Texture getTexture(String resourceName) throws IOException {
+ Texture tex = (Texture) table.get(resourceName);
+
+ if (tex != null) {
+ return tex;
+ }
+
+ tex = getTexture(resourceName,
+ GL11.GL_TEXTURE_2D, // target
+ GL11.GL_RGBA, // dst pixel format
+ GL11.GL_LINEAR, // min filter (unused)
+ GL11.GL_LINEAR);
+
+ table.put(resourceName,tex);
+
+ return tex;
+ }
+
+ /**
+ * Load a texture into OpenGL from a image reference on
+ * disk.
+ *
+ * @param resourceName The location of the resource to load
+ * @param target The GL target to load the texture against
+ * @param dstPixelFormat The pixel format of the screen
+ * @param minFilter The minimising filter
+ * @param magFilter The magnification filter
+ * @return The loaded texture
+ * @throws IOException Indicates a failure to access the resource
+ */
+ public Texture getTexture(String resourceName,
+ int target,
+ int dstPixelFormat,
+ int minFilter,
+ int magFilter) throws IOException {
+ int srcPixelFormat = 0;
+
+ // create the texture ID for this texture
+ int textureID = createTextureID();
+ Texture texture = new Texture(target,textureID);
+
+ // bind this texture
+ GL11.glBindTexture(target, textureID);
+
+ BufferedImage bufferedImage = loadImage(resourceName);
+ texture.setWidth(bufferedImage.getWidth());
+ texture.setHeight(bufferedImage.getHeight());
+
+ if (bufferedImage.getColorModel().hasAlpha()) {
+ srcPixelFormat = GL11.GL_RGBA;
+ } else {
+ srcPixelFormat = GL11.GL_RGB;
+ }
+
+ // convert that image into a byte buffer of texture data
+ ByteBuffer textureBuffer = convertImageData(bufferedImage,texture);
+
+ if (target == GL11.GL_TEXTURE_2D) {
+ GL11.glTexParameteri(target, GL11.GL_TEXTURE_MIN_FILTER, minFilter);
+ GL11.glTexParameteri(target, GL11.GL_TEXTURE_MAG_FILTER, magFilter);
+ }
+
+ // produce a texture from the byte buffer
+ GL11.glTexImage2D(target,
+ 0,
+ dstPixelFormat,
+ get2Fold(bufferedImage.getWidth()),
+ get2Fold(bufferedImage.getHeight()),
+ 0,
+ srcPixelFormat,
+ GL11.GL_UNSIGNED_BYTE,
+ textureBuffer );
+
+ return texture;
+ }
+
+ /**
+ * Get the closest greater power of 2 to the fold number
+ *
+ * @param fold The target number
+ * @return The power of 2
+ */
+ private int get2Fold(int fold) {
+ int ret = 2;
+ while (ret < fold) {
+ ret *= 2;
+ }
+ return ret;
+ }
+
+ /**
+ * Convert the buffered image to a texture
+ *
+ * @param bufferedImage The image to convert to a texture
+ * @param texture The texture to store the data into
+ * @return A buffer containing the data
+ */
+ private ByteBuffer convertImageData(BufferedImage bufferedImage,Texture texture) {
+ ByteBuffer imageBuffer = null;
+ WritableRaster raster;
+ BufferedImage texImage;
+
+ int texWidth = 2;
+ int texHeight = 2;
+
+ // find the closest power of 2 for the width and height
+ // of the produced texture
+ while (texWidth < bufferedImage.getWidth()) {
+ texWidth *= 2;
+ }
+ while (texHeight < bufferedImage.getHeight()) {
+ texHeight *= 2;
+ }
+
+ texture.setTextureHeight(texHeight);
+ texture.setTextureWidth(texWidth);
+
+ // create a raster that can be used by OpenGL as a source
+ // for a texture
+ if (bufferedImage.getColorModel().hasAlpha()) {
+ raster = Raster.createInterleavedRaster(DataBuffer.TYPE_BYTE,texWidth,texHeight,4,null);
+ texImage = new BufferedImage(glAlphaColorModel,raster,false,new Hashtable());
+ } else {
+ raster = Raster.createInterleavedRaster(DataBuffer.TYPE_BYTE,texWidth,texHeight,3,null);
+ texImage = new BufferedImage(glColorModel,raster,false,new Hashtable());
+ }
+
+ // copy the source image into the produced image
+ Graphics g = texImage.getGraphics();
+ g.setColor(new Color(0f,0f,0f,0f));
+ g.fillRect(0,0,texWidth,texHeight);
+ g.drawImage(bufferedImage,0,0,null);
+
+ // build a byte buffer from the temporary image
+ // that be used by OpenGL to produce a texture.
+ byte[] data = ((DataBufferByte) texImage.getRaster().getDataBuffer()).getData();
+
+ imageBuffer = ByteBuffer.allocateDirect(data.length);
+ imageBuffer.order(ByteOrder.nativeOrder());
+ imageBuffer.put(data, 0, data.length);
+ imageBuffer.flip();
+
+ return imageBuffer;
+ }
+
+ /**
+ * Load a given resource as a buffered image
+ *
+ * @param ref The location of the resource to load
+ * @return The loaded buffered image
+ * @throws IOException Indicates a failure to find a resource
+ */
+ private BufferedImage loadImage(String ref) throws IOException {
+ URL url = TextureLoader.class.getClassLoader().getResource(ref);
+
+ if (url == null) {
+ throw new IOException("Cannot find: " + ref);
+ }
+
+ BufferedImage bufferedImage = ImageIO.read(new BufferedInputStream(getClass().getClassLoader().getResourceAsStream(ref)));
+
+ return bufferedImage;
+ }
+}