|
/*
DEVELOPING GAME IN JAVA
Caracteristiques
Editeur : NEW RIDERS
Auteur : BRACKEEN
Parution : 09 2003
Pages : 972
Isbn : 1-59273-005-1
Reliure : Paperback
Disponibilite : Disponible a la librairie
*/
import java.awt.AWTException;
import java.awt.Color;
import java.awt.Component;
import java.awt.Cursor;
import java.awt.DisplayMode;
import java.awt.EventQueue;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.Image;
import java.awt.Point;
import java.awt.Robot;
import java.awt.Toolkit;
import java.awt.Transparency;
import java.awt.Window;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferStrategy;
import java.awt.image.BufferedImage;
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileReader;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import javax.sound.midi.InvalidMidiDataException;
import javax.sound.midi.MetaEventListener;
import javax.sound.midi.MetaMessage;
import javax.sound.midi.MidiSystem;
import javax.sound.midi.MidiUnavailableException;
import javax.sound.midi.Sequence;
import javax.sound.midi.Sequencer;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.Mixer;
import javax.sound.sampled.SourceDataLine;
import javax.sound.sampled.UnsupportedAudioFileException;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;
/**
* GameManager manages all parts of the game.
*/
public class TitleGame extends GameCore {
public static void main(String[] args) {
new TitleGame().run();
}
// uncompressed, 44100Hz, 16-bit, mono, signed, little-endian
private static final AudioFormat PLAYBACK_FORMAT = new AudioFormat(44100,
16, 1, true, false);
private static final int DRUM_TRACK = 1;
public static final float GRAVITY = 0.002f;
private Point pointCache = new Point();
private TileMap map;
private MidiPlayer midiPlayer;
private SoundManager soundManager;
private ResourceManager resourceManager;
private Sound prizeSound;
private Sound boopSound;
private InputManager inputManager;
private TileMapRenderer renderer;
private GameAction moveLeft;
private GameAction moveRight;
private GameAction jump;
private GameAction exit;
public void init() {
super.init();
// set up input manager
initInput();
// start resource manager
resourceManager = new ResourceManager(screen.getFullScreenWindow()
.getGraphicsConfiguration());
// load resources
renderer = new TileMapRenderer();
renderer.setBackground(resourceManager.loadImage("background.png"));
// load first map
map = resourceManager.loadNextMap();
// load sounds
soundManager = new SoundManager(PLAYBACK_FORMAT);
prizeSound = soundManager.getSound("sounds/prize.wav");
boopSound = soundManager.getSound("sounds/boop2.wav");
// start music
midiPlayer = new MidiPlayer();
Sequence sequence = midiPlayer.getSequence("sounds/music.midi");
midiPlayer.play(sequence, true);
toggleDrumPlayback();
}
/**
* Closes any resurces used by the GameManager.
*/
public void stop() {
super.stop();
midiPlayer.close();
soundManager.close();
}
private void initInput() {
moveLeft = new GameAction("moveLeft");
moveRight = new GameAction("moveRight");
jump = new GameAction("jump", GameAction.DETECT_INITAL_PRESS_ONLY);
exit = new GameAction("exit", GameAction.DETECT_INITAL_PRESS_ONLY);
inputManager = new InputManager(screen.getFullScreenWindow());
inputManager.setCursor(InputManager.INVISIBLE_CURSOR);
inputManager.mapToKey(moveLeft, KeyEvent.VK_LEFT);
inputManager.mapToKey(moveRight, KeyEvent.VK_RIGHT);
inputManager.mapToKey(jump, KeyEvent.VK_SPACE);
inputManager.mapToKey(exit, KeyEvent.VK_ESCAPE);
}
private void checkInput(long elapsedTime) {
if (exit.isPressed()) {
stop();
}
Player player = (Player) map.getPlayer();
if (player.isAlive()) {
float velocityX = 0;
if (moveLeft.isPressed()) {
velocityX -= player.getMaxSpeed();
}
if (moveRight.isPressed()) {
velocityX += player.getMaxSpeed();
}
if (jump.isPressed()) {
player.jump(false);
}
player.setVelocityX(velocityX);
}
}
public void draw(Graphics2D g) {
renderer.draw(g, map, screen.getWidth(), screen.getHeight());
}
/**
* Gets the current map.
*/
public TileMap getMap() {
return map;
}
/**
* Turns on/off drum playback in the midi music (track 1).
*/
public void toggleDrumPlayback() {
Sequencer sequencer = midiPlayer.getSequencer();
if (sequencer != null) {
sequencer.setTrackMute(DRUM_TRACK, !sequencer
.getTrackMute(DRUM_TRACK));
}
}
/**
* Gets the tile that a Sprites collides with. Only the Sprite's X or Y
* should be changed, not both. Returns null if no collision is detected.
*/
public Point getTileCollision(Sprite sprite, float newX, float newY) {
float fromX = Math.min(sprite.getX(), newX);
float fromY = Math.min(sprite.getY(), newY);
float toX = Math.max(sprite.getX(), newX);
float toY = Math.max(sprite.getY(), newY);
// get the tile locations
int fromTileX = TileMapRenderer.pixelsToTiles(fromX);
int fromTileY = TileMapRenderer.pixelsToTiles(fromY);
int toTileX = TileMapRenderer
.pixelsToTiles(toX + sprite.getWidth() - 1);
int toTileY = TileMapRenderer.pixelsToTiles(toY + sprite.getHeight()
- 1);
// check each tile for a collision
for (int x = fromTileX; x <= toTileX; x++) {
for (int y = fromTileY; y <= toTileY; y++) {
if (x < 0 || x >= map.getWidth() || map.getTile(x, y) != null) {
// collision found, return the tile
pointCache.setLocation(x, y);
return pointCache;
}
}
}
// no collision found
return null;
}
/**
* Checks if two Sprites collide with one another. Returns false if the two
* Sprites are the same. Returns false if one of the Sprites is a Creature
* that is not alive.
*/
public boolean isCollision(Sprite s1, Sprite s2) {
// if the Sprites are the same, return false
if (s1 == s2) {
return false;
}
// if one of the Sprites is a dead Creature, return false
if (s1 instanceof Creature && !((Creature) s1).isAlive()) {
return false;
}
if (s2 instanceof Creature && !((Creature) s2).isAlive()) {
return false;
}
// get the pixel location of the Sprites
int s1x = Math.round(s1.getX());
int s1y = Math.round(s1.getY());
int s2x = Math.round(s2.getX());
int s2y = Math.round(s2.getY());
// check if the two sprites' boundaries intersect
return (s1x < s2x + s2.getWidth() && s2x < s1x + s1.getWidth()
&& s1y < s2y + s2.getHeight() && s2y < s1y + s1.getHeight());
}
/**
* Gets the Sprite that collides with the specified Sprite, or null if no
* Sprite collides with the specified Sprite.
*/
public Sprite getSpriteCollision(Sprite sprite) {
// run through the list of Sprites
Iterator i = map.getSprites();
while (i.hasNext()) {
Sprite otherSprite = (Sprite) i.next();
if (isCollision(sprite, otherSprite)) {
// collision found, return the Sprite
return otherSprite;
}
}
// no collision found
return null;
}
/**
* Updates Animation, position, and velocity of all Sprites in the current
* map.
*/
public void update(long elapsedTime) {
Creature player = (Creature) map.getPlayer();
// player is dead! start map over
if (player.getState() == Creature.STATE_DEAD) {
map = resourceManager.reloadMap();
return;
}
// get keyboard/mouse input
checkInput(elapsedTime);
// update player
updateCreature(player, elapsedTime);
player.update(elapsedTime);
// update other sprites
Iterator i = map.getSprites();
while (i.hasNext()) {
Sprite sprite = (Sprite) i.next();
if (sprite instanceof Creature) {
Creature creature = (Creature) sprite;
if (creature.getState() == Creature.STATE_DEAD) {
i.remove();
} else {
updateCreature(creature, elapsedTime);
}
}
// normal update
sprite.update(elapsedTime);
}
}
/**
* Updates the creature, applying gravity for creatures that aren't flying,
* and checks collisions.
*/
private void updateCreature(Creature creature, long elapsedTime) {
// apply gravity
if (!creature.isFlying()) {
creature.setVelocityY(creature.getVelocityY() + GRAVITY
* elapsedTime);
}
// change x
float dx = creature.getVelocityX();
float oldX = creature.getX();
float newX = oldX + dx * elapsedTime;
Point tile = getTileCollision(creature, newX, creature.getY());
if (tile == null) {
creature.setX(newX);
} else {
// line up with the tile boundary
if (dx > 0) {
creature.setX(TileMapRenderer.tilesToPixels(tile.x)
- creature.getWidth());
} else if (dx < 0) {
creature.setX(TileMapRenderer.tilesToPixels(tile.x + 1));
}
creature.collideHorizontal();
}
if (creature instanceof Player) {
checkPlayerCollision((Player) creature, false);
}
// change y
float dy = creature.getVelocityY();
float oldY = creature.getY();
float newY = oldY + dy * elapsedTime;
tile = getTileCollision(creature, creature.getX(), newY);
if (tile == null) {
creature.setY(newY);
} else {
// line up with the tile boundary
if (dy > 0) {
creature.setY(TileMapRenderer.tilesToPixels(tile.y)
- creature.getHeight());
} else if (dy < 0) {
creature.setY(TileMapRenderer.tilesToPixels(tile.y + 1));
}
creature.collideVertical();
}
if (creature instanceof Player) {
boolean canKill = (oldY < creature.getY());
checkPlayerCollision((Player) creature, canKill);
}
}
/**
* Checks for Player collision with other Sprites. If canKill is true,
* collisions with Creatures will kill them.
*/
public void checkPlayerCollision(Player player, boolean canKill) {
if (!player.isAlive()) {
return;
}
// check for player collision with other sprites
Sprite collisionSprite = getSpriteCollision(player);
if (collisionSprite instanceof PowerUp) {
acquirePowerUp((PowerUp) collisionSprite);
} else if (collisionSprite instanceof Creature) {
Creature badguy = (Creature) collisionSprite;
if (canKill) {
// kill the badguy and make player bounce
soundManager.play(boopSound);
badguy.setState(Creature.STATE_DYING);
player.setY(badguy.getY() - player.getHeight());
player.jump(true);
} else {
// player dies!
player.setState(Creature.STATE_DYING);
}
}
}
/**
* Gives the player the speicifed power up and removes it from the map.
*/
public void acquirePowerUp(PowerUp powerUp) {
// remove it from the map
map.removeSprite(powerUp);
if (powerUp instanceof PowerUp.Star) {
// do something here, like give the player points
soundManager.play(prizeSound);
} else if (powerUp instanceof PowerUp.Music) {
// change the music
soundManager.play(prizeSound);
toggleDrumPlayback();
} else if (powerUp instanceof PowerUp.Goal) {
// advance to next map
soundManager.play(prizeSound, new EchoFilter(2000, .7f), false);
map = resourceManager.loadNextMap();
}
}
}
/**
* The EchoFilter class is a SoundFilter that emulates an echo.
*
* @see FilteredSoundStream
*/
class EchoFilter extends SoundFilter {
private short[] delayBuffer;
private int delayBufferPos;
private float decay;
/**
* Creates an EchoFilter with the specified number of delay samples and the
* specified decay rate.
* <p>
* The number of delay samples specifies how long before the echo is
* initially heard. For a 1 second echo with mono, 44100Hz sound, use 44100
* delay samples.
* <p>
* The decay value is how much the echo has decayed from the source. A decay
* value of .5 means the echo heard is half as loud as the source.
*/
public EchoFilter(int numDelaySamples, float decay) {
delayBuffer = new short[numDelaySamples];
this.decay = decay;
}
/**
* Gets the remaining size, in bytes, of samples that this filter can echo
* after the sound is done playing. Ensures that the sound will have decayed
* to below 1% of maximum volume (amplitude).
*/
public int getRemainingSize() {
float finalDecay = 0.01f;
// derived from Math.pow(decay,x) <= finalDecay
int numRemainingBuffers = (int) Math.ceil(Math.log(finalDecay)
/ Math.log(decay));
int bufferSize = delayBuffer.length * 2;
return bufferSize * numRemainingBuffers;
}
/**
* Clears this EchoFilter's internal delay buffer.
*/
public void reset() {
for (int i = 0; i < delayBuffer.length; i++) {
delayBuffer[i] = 0;
}
delayBufferPos = 0;
}
/**
* Filters the sound samples to add an echo. The samples played are added to
* the sound in the delay buffer multipied by the decay rate. The result is
* then stored in the delay buffer, so multiple echoes are heard.
*/
public void filter(byte[] samples, int offset, int length) {
for (int i = offset; i < offset + length; i += 2) {
// update the sample
short oldSample = getSample(samples, i);
short newSample = (short) (oldSample + decay
* delayBuffer[delayBufferPos]);
setSample(samples, i, newSample);
// update the delay buffer
delayBuffer[delayBufferPos] = newSample;
delayBufferPos++;
if (delayBufferPos == delayBuffer.length) {
delayBufferPos = 0;
}
}
}
}
/**
* A abstract class designed to filter sound samples. Since SoundFilters may use
* internal buffering of samples, a new SoundFilter object should be created for
* every sound played. However, SoundFilters can be reused after they are
* finished by called the reset() method.
* <p>
* Assumes all samples are 16-bit, signed, little-endian format.
*
* @see FilteredSoundStream
*/
abstract class SoundFilter {
/**
* Resets this SoundFilter. Does nothing by default.
*/
public void reset() {
// do nothing
}
/**
* Gets the remaining size, in bytes, that this filter plays after the sound
* is finished. An example would be an echo that plays longer than it's
* original sound. This method returns 0 by default.
*/
public int getRemainingSize() {
return 0;
}
/**
* Filters an array of samples. Samples should be in 16-bit, signed,
* little-endian format.
*/
public void filter(byte[] samples) {
filter(samples, 0, samples.length);
}
/**
* Filters an array of samples. Samples should be in 16-bit, signed,
* little-endian format. This method should be implemented by subclasses.
*/
public abstract void filter(byte[] samples, int offset, int length);
/**
* Convenience method for getting a 16-bit sample from a byte array. Samples
* should be in 16-bit, signed, little-endian format.
*/
public static short getSample(byte[] buffer, int position) {
return (short) (((buffer[position + 1] & 0xff) << 8) | (buffer[position] & 0xff));
}
/**
* Convenience method for setting a 16-bit sample in a byte array. Samples
* should be in 16-bit, signed, little-endian format.
*/
public static void setSample(byte[] buffer, int position, short sample) {
buffer[position] = (byte) (sample & 0xff);
buffer[position + 1] = (byte) ((sample >> 8) & 0xff);
}
}
/**
* A Creature is a Sprite that is affected by gravity and can die. It has four
* Animations: moving left, moving right, dying on the left, and dying on the
* right.
*/
abstract class Creature extends Sprite {
/**
* Amount of time to go from STATE_DYING to STATE_DEAD.
*/
private static final int DIE_TIME = 1000;
public static final int STATE_NORMAL = 0;
public static final int STATE_DYING = 1;
public static final int STATE_DEAD = 2;
private Animation left;
private Animation right;
private Animation deadLeft;
private Animation deadRight;
private int state;
private long stateTime;
/**
* Creates a new Creature with the specified Animations.
*/
public Creature(Animation left, Animation right, Animation deadLeft,
Animation deadRight) {
super(right);
this.left = left;
this.right = right;
this.deadLeft = deadLeft;
this.deadRight = deadRight;
state = STATE_NORMAL;
}
public Object clone() {
// use reflection to create the correct subclass
Constructor constructor = getClass().getConstructors()[0];
try {
return constructor
.newInstance(new Object[] { (Animation) left.clone(),
(Animation) right.clone(),
(Animation) deadLeft.clone(),
(Animation) deadRight.clone() });
} catch (Exception ex) {
// should never happen
ex.printStackTrace();
return null;
}
}
/**
* Gets the maximum speed of this Creature.
*/
public float getMaxSpeed() {
return 0;
}
/**
* Wakes up the creature when the Creature first appears on screen.
* Normally, the creature starts moving left.
*/
public void wakeUp() {
if (getState() == STATE_NORMAL && getVelocityX() == 0) {
setVelocityX(-getMaxSpeed());
}
}
/**
* Gets the state of this Creature. The state is either STATE_NORMAL,
* STATE_DYING, or STATE_DEAD.
*/
public int getState() {
return state;
}
/**
* Sets the state of this Creature to STATE_NORMAL, STATE_DYING, or
* STATE_DEAD.
*/
public void setState(int state) {
if (this.state != state) {
this.state = state;
stateTime = 0;
if (state == STATE_DYING) {
setVelocityX(0);
setVelocityY(0);
}
}
}
/**
* Checks if this creature is alive.
*/
public boolean isAlive() {
return (state == STATE_NORMAL);
}
/**
* Checks if this creature is flying.
*/
public boolean isFlying() {
return false;
}
/**
* Called before update() if the creature collided with a tile horizontally.
*/
public void collideHorizontal() {
setVelocityX(-getVelocityX());
}
/**
* Called before update() if the creature collided with a tile vertically.
*/
public void collideVertical() {
setVelocityY(0);
}
/**
* Updates the animaton for this creature.
*/
public void update(long elapsedTime) {
// select the correct Animation
Animation newAnim = anim;
if (getVelocityX() < 0) {
newAnim = left;
} else if (getVelocityX() > 0) {
newAnim = right;
}
|