lwjgl/src/java/org/lwjgl/examples/spaceinvaders/Game.java

603 lines
18 KiB
Java

/*
* 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.LWJGLException;
import org.lwjgl.Sys;
import org.lwjgl.input.Keyboard;
import org.lwjgl.input.Mouse;
import org.lwjgl.opengl.Display;
import org.lwjgl.opengl.DisplayMode;
import org.lwjgl.opengl.GL11;
/**
* 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 {
setDisplayMode();
Display.setTitle(WINDOW_TITLE);
Display.setFullscreen(fullscreen);
Display.create();
// grab the mouse, dont want that hideous cursor when we're playing!
Mouse.setGrabbed(true);
// enable textures since we're going to use these for our sprites
GL11.glEnable(GL11.GL_TEXTURE_2D);
// disable the OpenGL depth test since we're rendering 2D graphics
GL11.glDisable(GL11.GL_DEPTH_TEST);
GL11.glMatrixMode(GL11.GL_PROJECTION);
GL11.glLoadIdentity();
GL11.glOrtho(0, width, height, 0, -1, 1);
textureLoader = new TextureLoader();
// 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 = org.lwjgl.util.Display.getAvailableDisplayModes(width, height, -1, -1, -1, -1, 60, 60);
try {
org.lwjgl.util.Display.setDisplayMode(dm, new String[] {
"width=" + width,
"height=" + height,
"freq=" + 60,
"bpp=" + org.lwjgl.opengl.Display.getDisplayMode().getBitsPerPixel()
});
return true;
} catch (Exception e) {
e.printStackTrace();
System.out.println("Unable to enter fullscreen, continuing in windowed mode");
}
return false;
}
/**
* 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
Display.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) {
Display.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 (Display.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;
case Keyboard.KEY_RIGHT:
return
Keyboard.isKeyDown(Keyboard.KEY_RIGHT) ||
mouseX > 0;
case Keyboard.KEY_SPACE:
return
Keyboard.isKeyDown(Keyboard.KEY_SPACE) ||
Mouse.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[]) {
System.out.println("Use -fullscreen for fullscreen mode");
new Game((argv.length > 0 && argv[0].equalsIgnoreCase("-fullscreen"))).execute();
}
/**
*
*/
private void execute() {
gameLoop();
soundManager.destroy();
Display.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);
}
}