From 5aedbe9c7fab5da2afc6eedff644bf54879fd957 Mon Sep 17 00:00:00 2001 From: Brian Matzon Date: Sat, 12 Jun 2004 19:59:20 +0000 Subject: [PATCH] space invaders example --- .../examples/spaceinvaders/AlienEntity.java | 157 +++++ .../lwjgl/examples/spaceinvaders/Entity.java | 184 ++++++ .../lwjgl/examples/spaceinvaders/Game.java | 607 ++++++++++++++++++ .../examples/spaceinvaders/ShipEntity.java | 98 +++ .../examples/spaceinvaders/ShotEntity.java | 120 ++++ .../examples/spaceinvaders/SoundManager.java | 189 ++++++ .../lwjgl/examples/spaceinvaders/Sprite.java | 127 ++++ .../lwjgl/examples/spaceinvaders/Texture.java | 190 ++++++ .../examples/spaceinvaders/TextureLoader.java | 285 ++++++++ 9 files changed, 1957 insertions(+) create mode 100644 src/java/org/lwjgl/examples/spaceinvaders/AlienEntity.java create mode 100644 src/java/org/lwjgl/examples/spaceinvaders/Entity.java create mode 100644 src/java/org/lwjgl/examples/spaceinvaders/Game.java create mode 100644 src/java/org/lwjgl/examples/spaceinvaders/ShipEntity.java create mode 100644 src/java/org/lwjgl/examples/spaceinvaders/ShotEntity.java create mode 100644 src/java/org/lwjgl/examples/spaceinvaders/SoundManager.java create mode 100644 src/java/org/lwjgl/examples/spaceinvaders/Sprite.java create mode 100644 src/java/org/lwjgl/examples/spaceinvaders/Texture.java create mode 100644 src/java/org/lwjgl/examples/spaceinvaders/TextureLoader.java 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; + } +}