space invaders example

This commit is contained in:
Brian Matzon 2004-06-12 19:59:20 +00:00
parent 7c20070bf8
commit 5aedbe9c7f
9 changed files with 1957 additions and 0 deletions

View file

@ -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
}
}

View file

@ -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);
}

View file

@ -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.
*
* <p>
* NOTE:<br>
* 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:<br>
* <a href="http://www.cokeandcode.com/" target="_blank">http://www.cokeandcode.com</a>
* </p>
*
* @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);
}
}

View file

@ -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();
}
}
}

View file

@ -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;
}
}
}

View file

@ -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$
* <p>
* 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.
* </p>
* @author Brian Matzon <brian@matzon.dk>
* @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();
}
}
}

View file

@ -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();
}
}

View file

@ -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;
}
}
}

View file

@ -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;
}
}