package vooga.scroller.sprites; import java.awt.Dimension; import java.awt.Graphics2D; import java.awt.Image; import java.awt.Rectangle; import java.awt.geom.Point2D; import util.Location; import util.Vector; import vooga.scroller.sprites.state.SpriteState; import vooga.scroller.sprites.state.SpriteStateManager; import vooga.scroller.util.ISpriteView; /** * This class represents a shape that moves on its own. * * Note, Sprite is a technical term: * http://en.wikipedia.org/wiki/Sprite_(computer_graphics) * * @author Robert C. Duvall */ public class Sprite { // canonical directions for a collision public static final int RIGHT_DIRECTION = 0; public static final int UP_DIRECTION = 270; public static final int LEFT_DIRECTION = 180; public static final int DOWN_DIRECTION = 90; // default location private static final Location DEFAULT_LOCATION = new Location(0, 0); // state private Location myCenter; private Vector myVelocity; private Dimension mySize; private ISpriteView myView; // keep copies of the original state so shape can be reset as needed private Location myOriginalCenter; private Vector myOriginalVelocity; private Dimension myOriginalSize; private ISpriteView myOriginalView; // cached for efficiency private Rectangle myBounds; private Location myLastLocation; private Location myLastLocation2; private SpriteStateManager myStateManager; // private double myAngle; /** * Create a shape at the given position, with the given size. This is the constructor that * StaticEntities call. */ public Sprite (ISpriteView image, Dimension size) { this(image, DEFAULT_LOCATION, size, new Vector()); myStateManager = new SpriteStateManager(this); } public Sprite (ISpriteView image, Location center, Dimension size) { this(image, center, size, new Vector()); myStateManager = new SpriteStateManager(this); } /** * Create a shape at the given position, with the given size, velocity, and color. This is the * constructor that NonStaticEntities call. */ private Sprite (ISpriteView image, Location center, Dimension size, Vector velocity) { // make copies just to be sure no one else has access // myAngle = 0; mySize = size; myOriginalView = image; myOriginalCenter = new Location(center); myLastLocation = new Location(myOriginalCenter.x, myOriginalCenter.y); myLastLocation2 = new Location(myOriginalCenter.x, myOriginalCenter.y); myOriginalSize = new Dimension(size); myOriginalVelocity = new Vector(velocity); reset(); resetBounds(); } /** * Creates and returns a copy of this Sprite. Used for instantiating copies for use * with the LevelEditor. * * @return a copy of a sprite */ public Sprite copy () { try { return this.getClass().newInstance(); } catch (InstantiationException e) { return null; } catch (IllegalAccessException e) { return null; } } /** * Describes how to "animate" the shape by changing its state. * * Currently, moves by the current velocity. */ public void update (double elapsedTime, Dimension bounds) { myStateManager.update(elapsedTime, bounds); } /** * Moves shape's center by given vector. */ public void translate (Vector v) { myCenter.translate(v); resetBounds(); } /** * Resets shape's center. */ public void setCenter (double x, double y) { myCenter.setLocation(x, y); resetBounds(); } /** * Returns myCenter */ public Location getCenter () { return myCenter; } /** * Returns shape's x coordinate in pixels. */ public double getX () { return myCenter.getX(); } /** * Returns shape's y-coordinate in pixels. */ public double getY () { return myCenter.getY(); } /** * Returns shape's left-most coordinate in pixels. */ public double getLeft () { return myCenter.getX() - mySize.width / 2; } /** * Returns shape's top-most coordinate in pixels. */ public double getTop () { return myCenter.getY() - mySize.height / 2; } /** * Returns shape's right-most coordinate in pixels. */ public double getRight () { return myCenter.getX() + mySize.width / 2; } /** * Returns shape's bottom-most coordinate in pixels. */ public double getBottom () { return myCenter.getY() + mySize.height / 2; } /** * Returns shape's width in pixels. */ public double getWidth () { return mySize.getWidth(); } /** * Returns shape's height in pixels. */ public double getHeight () { return mySize.getHeight(); } /** * Scales shape's size by the given factors. */ public void scale (double widthFactor, double heightFactor) { mySize.setSize(mySize.width * widthFactor, mySize.height * heightFactor); resetBounds(); } /** * Resets shape's size. */ public void setSize (int width, int height) { mySize.setSize(width, height); resetBounds(); } /** * Returns shape's velocity. */ public Vector getVelocity () { return myVelocity; } /** * Resets shape's velocity. */ public void setVelocity (double angle, double magnitude) { myVelocity = new Vector(angle, magnitude); } /** * Resets shape's image. */ public void setView (ISpriteView image) { if (image != null) { myView = image; } } /** * Returns rectangle that encloses this shape. */ public Rectangle getBounds () { resetBounds(); return myBounds; // restBounds*) } /** * Returns true if the given point is within a rectangle representing this shape. */ public boolean intersects (Sprite other) { return getBounds().intersects(other.getBounds()); } public boolean intersects (Rectangle other) { return getBounds().intersects(other.getBounds()); } /** * Returns true if the given point is within a rectangle representing this shape. */ public boolean intersects (Point2D pt) { return getBounds().contains(pt); } /** * Reset shape back to its original values. */ public void reset () { myCenter = new Location(myOriginalCenter); mySize = new Dimension(myOriginalSize); myVelocity = new Vector(myOriginalVelocity); myView = myOriginalView.reset(); // TODO: reset paintable? } /** * Display this shape on the screen. */ public void paint (Graphics2D pen) { myStateManager.paint(pen); } /** * Display this shape translated on the screen, used for all Sprites besides Player */ public void paint (Graphics2D pen, Location loc, Location origLoc) { Location currentLocal = new Location(this.getCenter()); Location tempLocal = translate(loc, origLoc); this.setCenter(tempLocal.getX(), tempLocal.getY()); this.paint(pen); this.setCenter(currentLocal.getX(), currentLocal.getY()); // myView.paint(pen, translate(loc, origLoc), mySize, 0); } /** * The translates the the Location with respect to the Location given. * * @param loc the Location to translate in relation to * @return the translated Location */ private Location translate (Location loc, Location origLoc) { Location temp = new Location(myCenter.getX() - (loc.getX() - origLoc.getX()), myCenter.getY() - (loc.getY() - origLoc.getY())); return temp; } /** * Returns rectangle that encloses this shape. */ protected void resetBounds () { myBounds = new Rectangle((int) getLeft(), (int) getTop(), mySize.width, mySize.height); } /** * Returns approximate direction from center of rectangle to side which was hit or * NaN if no hit took place. */ protected double getHitDirection (Rectangle bounds) { // double angle = Vector.angleBetween(myCenter, new Location(bounds.getCenterX(), // bounds.getCenterY())); // BUGBUG: FIX ME --- this is very imperfect, but sort of works for now if (bounds.contains(new Location(getLeft(), getY()))) { return RIGHT_DIRECTION; } else if (bounds.contains(new Location(getX(), getBottom()))) { return UP_DIRECTION; } else if (bounds.contains(new Location(getRight(), getY()))) { return LEFT_DIRECTION; } else if (bounds.contains(new Location(getX(), getTop()))) { return DOWN_DIRECTION; } return 0; // return Double.NaN; } /** * Returns a view of this sprite -TODO: Maybe should be specified in an interface(?) */ public ISpriteView getView () { return myView; } /** * Gives the last location of this sprite. * * @return The location of the sprite at the previous update. */ public Location lastLocation () { return myLastLocation2; } /** * Returns the default image for this sprite. * * @return the default image of this sprite. */ public Image getDefaultImg () { return myOriginalView.getDefaultImg(); } /** * Sets the default image of this sprite. * * @param s is the ISpriteView that this sprite will use to paint itself. */ public void setDefaultImg (ISpriteView s) { myOriginalView = s; } /** * Adds a vector to this sprite's velocity. * * @param force is the vector to be added to this sprite. */ public void addVector (Vector force) { myVelocity.sum(force); } /** * Gives the dimensions of this sprite. * * @return the Size of this sprite in dimensions. */ public Dimension getSize () { return mySize; } /** * Sets the velocity of this sprite. * * @param velocity is the vector to set this sprite's velocity to. */ public void setVelocity (Vector velocity) { myVelocity = new Vector(velocity); } public void addPossibleState (int stateID, SpriteState state) { myStateManager.addState(stateID, state); } public void activateState (int stateID) { myStateManager.activateState(stateID); } public void deactivateState (int stateID) { myStateManager.deactivateState(stateID); } public void updateLastLocation () { myLastLocation2 = new Location(myLastLocation.x, myLastLocation.y); myLastLocation = new Location(myCenter.x, myCenter.y); } // public void rotate(double angle){ // myAngle += angle; // // normalizeAngle(); // } // // public void setAngle(double angle){ // myAngle = angle; // normalizeAngle(); // // } // // public double getAngle(){ // return myAngle; // } // // private void normalizeAngle(){ // while(myAngle > 360 | myAngle < 0){ // if(myAngle > 360){ // myAngle = myAngle%360; // } // if(myAngle < 0){ // myAngle += 360; // } // } // } }