Answered step by step
Verified Expert Solution
Link Copied!

Question

1 Approved Answer

This is a rather long Java project for a tower defense game, but my Enemy objects only spawn in the top-left of the game area

This is a rather long Java project for a tower defense game, but my Enemy objects only spawn in the top-left of the game area and my Tower objects can't be placed from the menu. Please show me any revised classes which can replace my faulty code. Thank you!

package game;

import java.awt.Graphics2D;

public interface Animatable { public void update(); public void draw(Graphics2D g); }

package game;

import java.awt.Graphics2D; import java.awt.Point;

public abstract class Effect implements Animatable { protected GameState game; protected Point pos; public Effect(GameState game, Point pos) { this.game = game; this.pos = pos; }

public void draw(Graphics2D g) { game.draw(g); } }

package game;

import java.awt.Graphics2D;

abstract public class Enemy { // Fields protected double percent; protected Path path; protected GameState game; public Enemy(Path path, GameState game) { this.game = new GameState(); this.path = ResourceLoader.getLoader().getPath("path_1.txt"); }

public void draw(Graphics2D g) { game.draw(g); } }

package game;

import java.awt.Graphics2D; import java.awt.Point; import java.awt.image.BufferedImage;

public class EnemySCargo extends Enemy implements Animatable { // Fields private double percent; private BufferedImage image; /** * This will create an EnemySCargo object to travel along * the path, at twice the speed of the EnemySnail object. * * @param path Path object which the EnemySCargo will travel * @param game GameState object which will interact with this * object */ public EnemySCargo(Path path, GameState game) { super(path, game); image = ResourceLoader.getLoader().getImage("s-cargo.png"); } //

@Override /** * Move the EnemySCargo 2% along the path. */ public void update() { percent += 0.002; if (percent > 1) { game.adjustLives(-1); game.removeAnimatable(this); } } /** * Draw the * * @param g Graphics2D object to draw the EnemySCargo on the path */ public void draw(Graphics2D g) { Point c = super.path.getPathPosition(percent); g.drawImage(image, c.x - 5, c.x - 5, null); } }

package game;

import java.awt.Graphics2D; import java.awt.Point; import java.awt.image.BufferedImage;

public class EnemySnail extends Enemy implements Animatable { private double percent; private GameState game; private BufferedImage image; public EnemySnail(Path path, GameState game) { super(path, game); image = ResourceLoader.getLoader().getImage("snail.png"); }

@Override public void update() { percent += 0.001; if (percent > 1) { game.adjustLives(-1); game.removeAnimatable(this); } }

public void draw(Graphics2D g) { Point c = super.path.getPathPosition(percent); g.drawImage(image, c.x - 5, c.x - 5, null); } }

package game;

import java.awt.*; import java.util.ArrayList; import java.util.List;

/** * This GameState method will be take care of loading * the BufferedImage and Path files, as well as * moving any objects along the selected path. */

public class GameState { private Path gardenPath; private ResourceLoader loader; private double objectPercentage; // Booleans indicate whether game is over, game is playing private boolean gameOver, gamePlaying; // Ints for credit and life counters private int credits, lives; // List for active, expired, waiting objects private List active; private List expired; private List temp; // Point for keeping track of mouseX and mouseY // Boolean for buttonPressed private Point mousePos; private boolean isClicked; // Salt menu object private Animatable saltMenu; /** * This will create a GameState object * with a ResourceLoader and an initial * position for the object. */ public GameState() { loader = ResourceLoader.getLoader(); gameOver = false; // Game is not over gamePlaying = true; // Game plays upon starting credits = 100; // Gives 100 credits lives = 10; // Starts with 10 lives // Initialize the active, expired, temp ArrayLists active = new ArrayList<>(); expired = new ArrayList<>(); temp = new ArrayList<>(); // Initialize gardenPath gardenPath = ResourceLoader.getLoader().getPath("path_1.txt"); // Initialize mouse position and clicked variables mousePos = new Point(0, 0); isClicked = false; // Initialize salt menu object active.add(new SaltTowerMenuItem(this, new Point(700, 300))); }

/** * Advance the object 0.1% along the path. */ public void update() { if (Math.random() < 0.05) { if (Math.random() < 0.01) active.add(new EnemySCargo(gardenPath, this)); else active.add(new EnemySnail(gardenPath, this)); } // Replace all expired Animatables with temp Animatables

active.addAll(temp); temp.clear(); active.removeAll(expired); expired.clear(); } public void draw(Graphics2D g) { /* Graphics objects are inconvenient. Fortunately, JPanels use * Graphics2D objects (a subclass of the Graphics class). * * The GUI documentation indicates that our object, g, is actually * a Graphics2D object, so cast it and use it as such. */ g.drawImage(loader.getImage("path_1.jpg"), 0, 0, null); // Draw the menu. g.setColor(Color.WHITE); g.fillRect(600, 0, 200, 600); // Draw the object, centered on its the path. (Must get its location first.) for (Animatable a : active) { a.draw(g); } // Draw credits and lives counter g.setColor(Color.BLACK); g.drawString("Lives: " + lives, 650, 200); g.drawString("Credits: " + credits, 650, 250); } public void removeAnimatable(Animatable e) { expired.add(e); } public void addAnimatable(Animatable n) { active.add(n); } public void adjustCredits(int amount) { credits += amount; } public void adjustLives(int amount) { lives += amount; }

public void setMousePos(Point p) { mousePos = new Point(p.x, p.y); }

public Point getMousePos() { return new Point(mousePos); }

public void setMouseClicked() { isClicked = true; } public void clearMouseClicked() { isClicked = false; } public boolean getMouseClicked() { return isClicked; } }

package game;

import java.awt.*; import java.util.*;

/** * A path represents a series of line segments along which an * enemy will travel. A path is a collection of line segments, * specified by their endpoints. (The end of one segment is the * start of the next.) * * This Path class is a convenient way to hide the details of the * path from our other code. We can simply ask a path object for * some coordinates along the path. * * I've combined my code that collects path points with this class * for convenience. See the additional functions beneath the draw * method. (The user can click mouse buttons to add/remove path points.) * * This is my example code from class, cleaned up and commented. */

/* Notice in the code below the way I line up commas, equal signs, etc. in * lines grouped by idea. I also take great care to line up the left * edges properly. Each scope should indent another 4 spaces. * It helps me read my code and avoid typos. */

public class Path { /* Fields (variables that will exist in every Path object) * * (I've decided to adapt this class to use lists instead of arrays. You * saw me start this in lecture, and I've finished the conversion.) * * Note: Sometimes 'List' is ambiguous. There are multiple * built-in List classes. When needed, use java.util.List below * to tell Eclipse which List class to use. */ private java.util.List coord; // Methods /** * This constructor builds a path by reading text from a Scanner. * The first Scanned item is the length of the path (in points), * and the remaining pairs of scanned text items are coordinate values * (as x, y pairs). * * @param in a Scanner ready to scan the integers that define this path */ public Path (Scanner in) { /* The first entry in the file should be the number of points. * Read it, save it in a local variable. (We don't need it for more * than a moment.) */ int pointCount = in.nextInt(); /* Create the List object (instead of using an array). Caution: List * is an abstract supertype. You must create a specific kind of list. * ArrayList objects will do nicely in this circumstance. We could * also use LinkedList objects, but we might suffer a performance hit. */ coord = new ArrayList(); // ArrayList objects are List objects. /* Read in the data points into our list. * In my code below, i represents 'items read'. It starts * at 0 (nothing read yet), but stops when i == size (everything read). */ for (int i = 0; i < pointCount; i++) { int x = in.nextInt(); int y = in.nextInt(); Point p = new Point(x, y); /* Array lists grow as needed. Adding something to the list * puts it at the end of the list. */ coord.add(p); } } /** * Returns the total length of this path. The length of the path * is the sum of length of the path segments. Since the coordinates * are unitless, this length is unitless. * * (Note: This method was called getLength, I changed the name for clarity.) * * @return the total length of this path */ public double getTotalLength () { // Compute the path length. double totalLength = 0;

/* If we count segments starting at 1, * segment 1 is path[0] ... path[1], and * segment n is path[n-1] ... path[n]. * * In an array or list, the last entry is indexed one less * than the length of the list, so as long as the index is * less than the length (or size) of the list, it is valid. * * Remember, for arrays use path.length. For List objects, use path.size() */ for (int i = 0; i < coord.size() - 1; i++) { Point start = coord.get(i); // Extract segment start/end Point end = coord.get(i + 1); /* A neat shortcut below, it is the same as * totalLength = totalLength + start.distance(end); */ totalLength += start.distance(end); } // Done. return totalLength; } /** * Returns a Point, or x, y coordinates, of some position along this * path. The position is given as a percentage. 0.0 means the * first position on the path, and 1.0 means the last position on the * path. * * @param percentage a distance along the path, as a percentage * @return the x, y coordinate (as a Point object) of this position on the path */ public Point getPathPosition (double percentage) { /* Caution: Do not return any Point object from the path. A Point * object can have its contents changed (such as setting x or y to 0). * If the caller got back a reference to one of our Point * objects, they could change x or y and screw up the path. * Instead, in the code below I either compute new Point objects, * or I return new points that are copies of points in our path. * * Debugging only: In my solution, I can have empty paths. This * statement deals with this. (This was not shown during lecture.) * Since we cannot look through non-existant path segments, exit early. */ if (coord.size() == 0) // empty path - return 0,0 return new Point(0, 0); /* Math is annoying when using doubles. We need to take * extra care. First, make sure that the percentage * is in the range (0.0...1.0), exclusive. If not, * return reasonable values. * * If the percentage is at or before the start of the path, * return the first path coordinate. If the percentage is past * the end, return the last path coordinate. */ if (percentage <= 0.0) return new Point(coord.get(0)); // Make a new Point object that copies ours if (percentage >= 1.0) return new Point(coord.get(coord.size()-1)); // Make a new Point object that copies ours

/* Convert the percentage to a distance. This is how far along * the path we are. We need to find the coordinate that is at this * distance from the start of the path. */ double distanceToTravel = getTotalLength() * percentage; /* Walk through the segments and keep track of the distance traveled as * we go. If the distance traveled is greater than or equal to * the amount we're supposed to travel, we've found the segment that * we're in. * * In the code below, I compute values in the loop I need after the loop. * I declare my local variables before the loop (so they'll still be there * after the loop), but they need initial values. * * Update: Because my solution allows incomplete paths, the segment length * must be non-zero. The loop may never loop, and the segment length * might be used as a divisor below. Also, I rely on the start and * end points being valid, so they must be the first point in the list. * (Consider what would happen below if the path had only one point.) */ Point start = coord.get(0); // Segment points for the current segment. Point end = coord.get(0); double totalDistance = 0; // Accumulated distance double segmentLength = 1; // Length of the current segment. for (int i = 0; i < coord.size() - 1; i++) { // Extract segment start/end points start = coord.get(i); end = coord.get(i + 1);

// Compute the length of this segment, combine it with the total. segmentLength = start.distance(end); totalDistance = totalDistance + segmentLength; // Better: totalDistance += segmentLength; // If we've gone far enough (or too far), exit the loop immediately. if (totalDistance > distanceToTravel) break; } /* Consider the current segment, not the entire path. The distance we are * seeking is in this segment somewhere. Calculate how much too far * the end of the segment is. Then, see what percentage of this segment * the excess distance is. * * Since the distanceToTravel is no greater than totalDistance, the * result will be non-negative. */ double excessDistance = totalDistance - distanceToTravel; double segmentPercentage = excessDistance / segmentLength; // Will be between [0..1]

/* We know that the target coordinate is some segmentPercentage from the segment end, * and (1 - segmentPercentage) from the segment start. Use these to compute a * weighted average of the start and end points. I've broken this * code into separate lines for clarity. * * Remember, if we're 0% from the end, we're at the end, so include 100% of the end. * (Vice versa for the start.) */ double targetX = (segmentPercentage)*start.x + (1-segmentPercentage)*end.x; double targetY = (segmentPercentage)*start.y + (1-segmentPercentage)*end.y; Point result = new Point ((int) targetX, (int) targetY); // Done, return the coordinates (as a Point object). return result; } /* Below this point: Debugging and development functions only. * These are not intended for use during the game, but they do help * during development. */

/** * Draws the path to the screen in a highly visible * color. Draw it a bit thick to make it easier to see. * * I've adjusted the parameter of this function to require * a Graphics2D object. This allows us to set line thickness. * * This function is for debugging only - it is not intended for * general use. * * @param k any Graphics2D object suitable for drawing */ public void draw (Graphics2D k) { // Set the color and line thickness. k.setColor (Color.BLACK); k.setStroke(new BasicStroke(3)); /* Draw the segments. I really didn't like the way I did it in class, * so I've updated it to take advantage of the fact that we can * control line widths with Graphics2D objects. */ for (int i = 0; i < coord.size() - 1; i++) { Point start = coord.get(i); // Extract segment start/end Point end = coord.get(i + 1); k.drawLine(start.x, start.y, end.x, end.y); } }

/** * This constructor builds an empty path. It is intended for * use in development stages of the project. (The user can then 'add' * additional points to the path, or print out the path coordinates.) * * This constructor should probably not be used during the game, but * it could be if you wanted to create dynamic paths. */ public Path () { /* Just create an empty list of points. * Even though the path is empty, the List object is still * needed. It will have 0 points in it. */ coord = new ArrayList(); // ArrayList objects are List objects. } /** * Adds a point to the end of the path. * * @param p a point to add */ public void addPathPoint (Point p) { coord.add(p); printPath(); } /** * Removes the last point in this path, if any. */ public void removeLastPathPoint () { /* Make sure there is at least one point, we cannot remove an entry * from an empty list. */ if (coord.size() > 0) { coord.remove(coord.size() - 1); // Remove the point at the specified position. printPath(); } } /** * Prints the path to the console for debugging. * This function makes it easier to create path text files. * Simply copy the last numeric output into a file. */ public void printPath () { System.out.println (); System.out.println ("The current path: (Don't copy this line or anything above it.)"); System.out.println (); System.out.println (coord.size()); for (Point p : coord) System.out.printf ("%4d %4d ", p.x, p.y); /* You've not seen printf, and I'm not teaching it in class. I * use it above to line up the coordinates into nice columns in * the output. Look up Java printf for more details, it's very useful. */ } }

package game;

import java.awt.image.BufferedImage; import java.io.InputStream; import java.util.*;

/** * This will take care of loading the images * and paths for the TowerDefense application. */

public class ResourceLoader { private HashMap image; private static ResourceLoader instance; private ResourceLoader() { image = new HashMap<>(); } /** * This will take a String for an image file, then * returns the BufferedImage in the "resources" * package. On any errors, it will print an * error to the console and exit. * * @param s A String representing the object file. * @return A BufferedImage for the backdrop. */ public BufferedImage getImage(String s) { String resourceName = "resources/" + s; BufferedImage backdrop; try { if (image.containsKey(resourceName)) { return image.get(resourceName); } else { // Locate the file. Java gives us back an 'InputStream' object. ClassLoader myLoader = this.getClass().getClassLoader(); InputStream imageStream = myLoader.getResourceAsStream(resourceName); // Use it to read the image. backdrop = javax.imageio.ImageIO.read(imageStream); // A handy helper method image.put(resourceName, backdrop); return backdrop; } } catch (Exception e) { /* On any error, just print a message and exit. * (You should make sure the files are in the correct place.) */ System.err.println ("Could not load the background image file: " + resourceName); System.exit(0); // Bail out. } return null; // This should not reach this point

} /** * This will be able to take a String and return it with a Path object for * the enemies to travel. * * @param s A String for the Path object to be located in the "resources" * package. * @return The Path object from the text object. */ public Path getPath(String s) { Path gardenPath;

ClassLoader myLoader = this.getClass().getClassLoader(); InputStream pointStream = myLoader.getResourceAsStream("resources/" + s); /* If the file does not exist, the InputStream is null. If so, build * an empty path. If not, build a path using a Scanner. * * Note: This is a little different than what I did in class. I'm * doing this to allow the code to run even if a path does not exist. * The user can click the mouse to create path points. */ if (pointStream == null) gardenPath = new Path(); else { Scanner in = new Scanner (pointStream); // Scan from the text file. gardenPath = new Path(in); in.close(); } return gardenPath; } /** * This will ensure that only ResourceLoader object will be created. * @return Only one ResourceLoader object. */ public static ResourceLoader getLoader() { if (instance == null) instance = new ResourceLoader(); return instance; } }

package game;

import java.awt.Graphics2D; import java.awt.Point; import java.awt.image.BufferedImage;

public class SaltTower extends Tower { private BufferedImage image; public SaltTower(GameState game, Point pos) { super(game, pos); image = ResourceLoader.getLoader().getImage("salt.png"); } /* * Right now, the update() method will be empty, * for it will be stationary and will not be * doing anything. */

@Override public void update() {} /** * Draw the tower. * * @param Graphics2D Graphics2D object for drawing * the tower. */ public void draw(Graphics2D g) { g.drawImage(image, pos.x, pos.y, null); } }

package game;

import java.awt.Graphics2D; import java.awt.Point; import java.awt.image.BufferedImage;

public class SaltTowerMenuItem extends Effect { private BufferedImage image;

public SaltTowerMenuItem(GameState game, Point pos) { super(game, pos); image = ResourceLoader.getLoader().getImage("salt.png"); }

@Override public void update() { if (game.getMousePos().distance(pos) < 50 && (game.getMouseClicked())) { game.addAnimatable(new SaltTower(game, pos)); game.clearMouseClicked(); } } /** * Draw the tower menu item. * * @param Graphics2D Graphics2D object for drawing * the tower menu. */ public void draw(Graphics2D g) { g.drawImage(image, pos.x, pos.y, null); } }

package game;

import java.awt.Point;

public class SaltTowerMovable extends Effect {

public SaltTowerMovable(GameState game, Point pos) { super(game, pos); }

@Override public void update() { pos = game.getMousePos(); if(game.getMouseClicked()) { game.addAnimatable(new SaltTower(game, pos)); game.removeAnimatable(this); game.clearMouseClicked(); } } }

package game;

import java.awt.Graphics2D; import java.awt.Point;

public abstract class Tower implements Animatable { protected GameState game; protected Point pos; public Tower(GameState game, Point pos) { this.game = new GameState(); this.pos = pos; }

public void draw(Graphics2D g) { game.draw(g); } }

package game;

import java.awt.*; import java.awt.event.*; import javax.swing.*;

/** * A path test panel is a GUI panel that displays a tower * defense path on the screen, and animates a small object * moving along the path. * * This class won't be part of the final project - we're just * using it for testing. */ public class TowerDefense extends JPanel implements ActionListener, MouseListener, Runnable, MouseMotionListener { /* This constant avoids an obnoxious warning, but it is totally unused by us. * It would only be relevant if we were using object serialization. */ private static final long serialVersionUID = 42L; // Fields (object variables) private Path gardenPath; private GameState game; // Methods public static void main (String[] args) { /* Main runs in the 'main' execution thread, and the GUI * needs to be built by the GUI execution thread. * Ask the GUI thread to run our 'run' method (at some * later time). */ SwingUtilities.invokeLater(new TowerDefense());

/* Done. Let the main thread of execution finish. All the * remaining work will be done by the GUI thread. */ } /** * Builds the GUI for this application. This method must * only be called/executed by the GUI thread. */ public void run () { /* InputStream objects are an alternative to File objects. * I use them to make it easier to locate resources - resources * can be in a directory, .jar, on the web, etc.. * * In the code below, I figure out * where my class was loaded from, and get the 'resource' from the * adjoining resource directory. Java will return an 'InputStream' * that you can use to read or scan through the resource. * * Get the object that loaded this class, because it keeps track * of -where- things are loaded from for us. (A very advanced * technique, please use this code.) */ // Set the size of this panel. Dimension panelSize = new Dimension(800, 600); this.setMinimumSize(panelSize); this.setPreferredSize(panelSize); this.setMaximumSize(panelSize); JFrame f = new JFrame("Tower Defense"); f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); f.setContentPane(this); f.pack(); f.setLocationRelativeTo(null); // Centers window f.setVisible(true); game = new GameState(); /* Create a timer (for animation), have it call our actionPerformed * method 60 times a second. (We must start it.) */ Timer t = new Timer(17, this); // The second parameter is the object to call t.start(); // Listen to our own mouse button presses. this.addMouseListener(this); this.addMouseMotionListener(this); }

/** * Draws the image, path, and the animating ball. * * (The background is not cleared, it is assumed the backdrop * fills the panel.) * * @param g the graphics object for drawing on this panel */ @Override public void paint (Graphics g) { game.draw((Graphics2D) g); // Call the draw method from the GameState class } /** * The actionPerformed method is called (from the GUI event loop) * whenever an action event occurs that this object is lisening to. * * For our test panel, we assume that the Timer has expired, so * we advance our small sphere along the path. * * @param e the event object */ @Override public void actionPerformed (ActionEvent e) { game.update(); // Call the update method from the GameState class repaint(); } /** * The mousePressed event is called when a mouse button is first pressed. * If the click is a left-button, a point is added to the path. If the * click is a right-button, a point is removed from the path. * * @param e the event object */ @Override public void mousePressed(MouseEvent e) { game.setMousePos(new Point(e.getX(), e.getY())); if (! game.getMouseClicked()) game.setMouseClicked(); }

/* Unused mouse events - notice empty bodies on the same line. * We need these methods because we implement MouseListener, * but we don't use them. */ @Override public void mouseClicked (MouseEvent e) {} @Override public void mouseEntered (MouseEvent e) {} @Override public void mouseExited (MouseEvent e) {} @Override public void mouseReleased(MouseEvent e){}

@Override public void mouseDragged(MouseEvent e) { game.setMouseClicked(); game.setMousePos(new Point(e.getX(), e.getY())); }

@Override public void mouseMoved(MouseEvent e) { game.setMousePos(new Point(e.getX(), e.getY())); } }

Step by Step Solution

There are 3 Steps involved in it

Step: 1

blur-text-image

Get Instant Access to Expert-Tailored Solutions

See step-by-step solutions with expert insights and AI powered tools for academic success

Step: 2

blur-text-image

Step: 3

blur-text-image

Ace Your Homework with AI

Get the answers you need in no time with our AI-driven, step-by-step assistance

Get Started

Recommended Textbook for

More Books

Students also viewed these Databases questions

Question

Prepare for a successful job interview.

Answered: 1 week ago

Question

Describe barriers to effective listening.

Answered: 1 week ago

Question

List the guidelines for effective listening.

Answered: 1 week ago