// NightHunter Class // NightHunter.java /** Project 3, CIS 525 Fall 2000 By Terrence Burrows "Night Hunter" Java game. The user can start a new game by typing in "n" or "N" and toggle the music on/off by typing "m" or "M". This Java program is setup to run only as a "Java Applet" in a web browser and NOT as a standalone Java application. The framework for this program is by Michael Morrison from the book "Teach yourself Internet Game Programming with Java in 21 days". See chapters 6 through 12. */ // Imports import java.awt.*; import java.applet.*; import java.util.Random; /** This is the main class that is instantiated to begin the "Night Hunter" Java game. This class handles high-level animation, game timing loop, sound issues, and user input. */ public class NightHunter extends Applet implements Runnable { private Image offImage, back, gunImage; private AudioClip music, gunsound, applause; private AudioClip[] clip = new AudioClip[5]; private Graphics offGrfx; private Thread animate; private MediaTracker tracker; private SRVector srv; // Game Background /** Mouse/Gun image */ private Sprite gun; /** Animation frame rate = 12 FPS */ private int delay = 83; private Font infoFont = new Font("Helvetica",Font.PLAIN, 14); private FontMetrics infoMetrics; private Random rand = new Random(System.currentTimeMillis()); private boolean musicOn = true; private static int level, killed; public static int lost; public String getAppletInfo() { return (new String("Night Hunter by Terrence Burrows")); } /** Game initialization method - starts the MediaTracker (needed to make sure images are completely loaded), load game background the mouse - gun images, and load audio clips. */ public void init() { /** Load and track the images with the MediaTracker */ tracker = new MediaTracker(this); back = getImage(getCodeBase(), "Res/Back2.gif"); tracker.addImage(back, 0); gunImage = getImage(getCodeBase(), "Res/Gun.gif"); tracker.addImage(gunImage, 0); Eyes.initResources(this, tracker, 0); // Load the audio clips music = getAudioClip(getCodeBase(), "Res/Music.au"); applause = getAudioClip(getCodeBase(), "Res/Applause.au"); gunsound = getAudioClip(getCodeBase(), "Res/Biggun.au"); clip[0] = getAudioClip(getCodeBase(), "Res/Crow.au"); clip[1] = getAudioClip(getCodeBase(), "Res/Hyena.au"); clip[2] = getAudioClip(getCodeBase(), "Res/Monkey.au"); clip[3] = getAudioClip(getCodeBase(), "Res/Tiger.au"); clip[4] = getAudioClip(getCodeBase(), "Res/Wolf.au"); } /** Standard thread start handler method */ public void start() { if (animate == null) { animate = new Thread(this); animate.start(); } } /** Standard thread termination handler method - *** added call to stop music when thread terminates */ public void stop() { if (animate != null) { animate.stop(); animate = null; } music.stop(); //make sure to terminate music } /** When an object is derived from a "Runnable" class then this method is called right after the thread "start()" method completes. *** This method is the heart of the game because this is where a new game is set up and this is where the game timing loop is implemented. */ public void run() { try { tracker.waitForID(0); } catch (InterruptedException e) { return; } // Setup a new game newGame(); long t = System.currentTimeMillis(); //get current time // While the thread is active update() and repaint() everything // every "delay" milliseconds. while (Thread.currentThread() == animate) { srv.update(); repaint(); try { t += delay; Thread.sleep(Math.max(0, t - System.currentTimeMillis())); } catch (InterruptedException e) { break; } } } /** This image update method uses "double-buffering" for bitmap images to eliminate screen flicker. Draw the screen image as well as game info (level etc.). If game is over display "Game Over" message and stop music. *** This is where new creatures get added randomly depending on game level. */ public void update(Graphics g) { // Create the offscreen graphics context if (offGrfx == null) { offImage = createImage(size().width, size().height); offGrfx = offImage.getGraphics(); infoMetrics = offGrfx.getFontMetrics(infoFont); } // Draw the sprites - includes the background as well // bitmap images srv.draw(offGrfx); // Draw the game info offGrfx.setFont(infoFont); offGrfx.setColor(Color.white); offGrfx.drawString(new String("Level: " + level + " Killed: " + killed + " Got Away: " + lost), 10, 5 + infoMetrics.getAscent()); // Is the game over? if (lost >= 5) { Font f = new Font("Helvetica", Font.BOLD, 36); FontMetrics fm = offGrfx.getFontMetrics(f); String s = new String("Game Over"); offGrfx.setFont(f); offGrfx.drawString(s, (size().width - fm.stringWidth(s)) / 2, ((size().height - fm.getHeight()) / 2) + fm.getAscent()); // Stop the music music.stop(); } else // Add a new creature? if ((rand.nextInt() % (20 - level / 2)) == 0) srv.add(new Eyes(this, 1 - Math.abs(rand.nextInt() % 2), level, Math.abs(rand.nextInt() % 200))); // Draw the image onto the screen g.drawImage(offImage, 0, 0, null); } /** Paint method that uses the Media Tracker to insure images are loaded. If images are not completely loaded then a message will be displayed to the user until the image is loaded. */ public void paint(Graphics g) { if ((tracker.statusID(0, true) & MediaTracker.ERRORED) != 0) { // Draw the error rectangle g.setColor(Color.red); g.fillRect(0, 0, size().width, size().height); return; } //check if images are loaded if ((tracker.statusID(0, true) & MediaTracker.COMPLETE) != 0) { // Image loaded - Draw the offscreen image g.drawImage(offImage, 0, 0, null); } else { // Image not loaded - Draw the title message // until the images load) Font f1 = new Font("TimesRoman", Font.BOLD, 28), f2 = new Font("Helvetica", Font.PLAIN, 16); FontMetrics fm1 = g.getFontMetrics(f1), fm2 = g.getFontMetrics(f2); String s1 = new String("Night Hunter"), s2 = new String("Loading images..."); g.setFont(f1); g.drawString(s1, (size().width - fm1.stringWidth(s1)) / 2, ((size().height - fm1.getHeight()) / 2) + fm1.getAscent()); g.setFont(f2); g.drawString(s2, (size().width - fm2.stringWidth(s2)) / 2, size().height - fm2.getHeight() - fm2.getAscent()); } } /** Mouse Enter Event - when mouse enters game frame show gun crosshairs. */ public boolean mouseEnter(Event evt, int x, int y) { if (gun != null) gun.show(); return true; } /** Mouse Exit Event - when mouse exits game frame hide gun crosshairs. */ public boolean mouseExit(Event evt, int x, int y) { if (gun != null) gun.hide(); return true; } /** Mouse Move Event - update gun crosshair position. */ public boolean mouseMove(Event evt, int x, int y) { if (gun != null) gun.setPosition(new Point(x - 10, y - 10)); return true; } /** Mouse Down Event - check if mouse/gun is over sprite, if it is kill sprite. Play gun sound and sprite death sound if hit. */ public boolean mouseDown(Event evt, int x, int y) { if (lost < 5) { //Sprite s = srv.isPointInside(new Point(x - 5, y - 5)); Sprite s = srv.isPointInside(new Point(x , y )); if (s != null) { // Remove the creature and increase number killed srv.removeElement(s); if ((++killed % 15) == 0) { // Increase the level and play applause sound level++; applause.play(); } else // Play the gun sound and creature hit sound gunsound.play(); //select random creature sound clip[Math.abs(rand.nextInt() % 5)].play(); } else // Play the gun sound gunsound.play(); } return true; } /** Keyboard keyDown Event - check for user input for new game or toggle music on/off. */ public boolean keyDown(Event evt, int key) { if ((key == (int)'n') || (key == (int)'N')) newGame(); else if ((key == (int)'m') || (key == (int)'M')) { musicOn = !musicOn; if (musicOn) music.loop(); else music.stop(); } return true; } /** Setup a new game. */ void newGame() { level = 1; killed = lost = 0; srv = new SRVector(new ImageBackground(this, back)); gun = new Sprite(this, gunImage, new Point((size().width - gunImage.getWidth(this)) / 2, (size().height - gunImage.getHeight(this)) / 2), new Point(0, 0), 20, Sprite.BA_WRAP); srv.add(gun); if (musicOn) music.loop(); // Command music to loop repeatidly } }