Package com.golden.gamedev.object

Source Code of com.golden.gamedev.object.Sprite

/*
* Copyright (c) 2008 Golden T Studios.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
package com.golden.gamedev.object;

// JFC
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.util.Comparator;

import com.golden.gamedev.object.collision.CollisionRect;
import com.golden.gamedev.object.collision.CollisionShape;

/**
* <code>Sprite</code> is the object in game that has graphical look and has
* its own behaviour.
* <p>
*
* Every sprite is lived in a background, by default sprite is attached to
* {@linkplain Background#getDefaultBackground default background}, always
* remember to set the sprite to the game background or use {@link SpriteGroup}
* class in {@link PlayField} to take care the sprite background set
* automatically.
* <p>
*
* Sprite is located somewhere in the background, to set sprite location simply
* use {@linkplain #setLocation(double, double)}. <br>
* And to move the sprite use either by moving the sprite directly by using
* {@linkplain #move(double, double)} or give speed to the sprite by using
* {@linkplain #setSpeed(double, double)}.
* <p>
*
* In conjunction with sprite group/playfield, every sprite has active state,
* this active state that determine whether the sprite is alive or not. Thus to
* remove a sprite from playfield, simply set the sprite active state to false
* by using {@linkplain #setActive(boolean) setActive(false)}.
* <p>
*
* To create sprite behaviour, always use {@link Timer} class utility in order
* to make the sprite behaviour independent of frame rate.
*
* @see com.golden.gamedev.object.SpriteGroup
* @see com.golden.gamedev.object.PlayField
* @see com.golden.gamedev.object.Timer
*/
public class Sprite implements java.io.Serializable {
 
  // /////// optimization /////////
  // private final Rectangle collisionOffset = new Rectangle(0,0,0,0); //
  // offset collision
 
  /**
   *
   */
  private static final long serialVersionUID = -4499098097309229784L;
 
  /** ************************** SPRITE BACKGROUND **************************** */
 
  private Background background;
 
  /** *************************** SPRITE POSITION ***************************** */
 
  private double x, y;
  private double horizontalSpeed, verticalSpeed; // in pixels per millisecond
  private double oldX, oldY; // old position before this sprite moves
         
  // ///////// optimization ///////////
  private static double screenX, screenY; // screen position = x-background.x
         
  /** **************************** SPRITE IMAGES ****************************** */
 
  private transient BufferedImage image;
 
  /**
   * The width of this sprite.
   */
  protected int width;
 
  /**
   * The height of this sprite.
   */
  protected int height;
 
  /**
   * Default collision shape used in {@link #getDefaultCollisionShape()}, can
   * be used in along with collision manager.
   */
  protected CollisionShape defaultCollisionShape = null;
 
  /** **************************** SPRITE FLAGS ******************************* */
 
  private int id; // to differentiate a sprite with another
  private Object dataID;
 
  private int layer; // for layering purpose only
 
  private boolean active = true;
  private boolean immutable; // immutable sprite won't be disposed/thrown
 
  // from its group
 
  /** ************************************************************************* */
  /** ***************************** CONSTRUCTOR ******************************* */
  /** ************************************************************************* */
 
  /**
   * Creates new <code>Sprite</code> with specified image and location.
   */
  public Sprite(BufferedImage image, double x, double y) {
    // init variables
    this.x = this.oldX = x;
    this.y = this.oldY = y;
   
    // sprite image
    if (image != null) {
      this.image = image;
      this.width = image.getWidth();
      this.height = image.getHeight();
    }
   
    this.background = Background.getDefaultBackground();
  }
 
  /**
   * Creates new <code>Sprite</code> with specified image and located at (0,
   * 0).
   * <p>
   *
   * @see #setLocation(double, double)
   */
  public Sprite(BufferedImage image) {
    this(image, 0, 0);
  }
 
  /**
   * Creates new <code>Sprite</code> with specified position and null image.
   * <p>
   *
   * <b>Note: the image must be set before rendering by using
   * {@link #setImage(BufferedImage)}.</b>
   *
   * @see #setImage(BufferedImage)
   */
  public Sprite(double x, double y) {
    this(null, x, y);
  }
 
  /**
   * Creates new <code>Sprite</code> with null image and located at (0, 0).
   * <p>
   *
   * <b>Note: the image must be set before rendering by using
   * {@link #setImage(BufferedImage)}.</b>
   *
   * @see #setImage(BufferedImage)
   * @see #setLocation(double, double)
   */
  public Sprite() {
    this(0, 0);
  }
 
  /** ************************************************************************* */
  /** *********************** SPRITE BACKGROUND ******************************* */
  /** ************************************************************************* */
 
  /**
   * Associates specified background with this sprite.
   */
  public void setBackground(Background backgr) {
    this.background = backgr;
    if (this.background == null) {
      this.background = Background.getDefaultBackground();
    }
  }
 
  /**
   * Returns the background where this sprite lived.
   */
  public Background getBackground() {
    return this.background;
  }
 
  /** ************************************************************************* */
  /** ************************ IMAGE OPERATION ******************************** */
  /** ************************************************************************* */
 
  /**
   * Returns the image of this sprite.
   */
  public BufferedImage getImage() {
    return this.image;
  }
 
  /**
   * Sets specified image as this sprite image.
   */
  public void setImage(BufferedImage image) {
    this.image = image;
   
    this.width = this.height = 0;
    if (image != null) {
      this.width = image.getWidth();
      this.height = image.getHeight();
    }
  }
 
  /**
   * Returns the width of this sprite.
   */
  public int getWidth() {
    return this.width;
  }
 
  /**
   * Returns the height of this sprite.
   */
  public int getHeight() {
    return this.height;
  }
 
  /**
   * Returns default {@linkplain #defaultCollisionShape collision shape}, can
   * be used along with collision manager.
   */
  public CollisionShape getDefaultCollisionShape() {
    if (this.defaultCollisionShape == null) {
      this.defaultCollisionShape = new CollisionRect();
    }
   
    this.defaultCollisionShape.setBounds(this.getX(), this.getY(), this
            .getWidth(), this.getHeight());
   
    return this.defaultCollisionShape;
  }
 
  /** ************************************************************************* */
  /** ********************** MOVEMENT OPERATION ******************************* */
  /** ************************************************************************* */
 
  /**
   * Attempts to move this sprite to specified <code>xs</code>,
   * <code>ys</code> location, if the sprite is right on specified location,
   * this method will return true.
   * <p>
   *
   * For example :
   *
   * <pre>
   *    Sprite s;
   *    public void update(long elapsedTime) {
   *       // move sprite to 100, 100 with speed 0.1 pixel in a millisecond
   *       if (s.moveTo(elapsedTime, 100, 100, 0.1) {
   *          // sprite has arrived to 100, 100
   *       }
   *    }
   * </pre>
   */
  public boolean moveTo(long elapsedTime, double xs, double ys, double speed) {
    if (this.x == xs && this.y == ys) {
      return true;
    }
   
    double angle = 90 + Math
            .toDegrees(Math.atan2(ys - this.y, xs - this.x));
    double radians = Math.toRadians(angle);
   
    double vx = Math.sin(radians) * speed * elapsedTime, vy = -Math
            .cos(radians)
            * speed * elapsedTime;
   
    boolean arriveX = false, arriveY = false;
   
    // checking x coordinate
    if (vx != 0) {
      if (vx > 0) {
        // moving right
        if (this.x + vx >= xs) {
          vx = xs - this.x; // snap
          arriveX = true;
        }
       
      }
      else {
        // moving left
        if (this.x + vx <= xs) {
          vx = xs - this.x; // snap
          arriveX = true;
        }
      }
     
    }
    else if (this.x == xs) {
      arriveX = true;
    }
   
    // checking y coordinate
    if (vy != 0) {
      if (vy > 0) {
        // moving down
        if (this.y + vy >= ys) {
          vy = ys - this.y; // snap
          arriveY = true;
        }
       
      }
      else {
        // moving up
        if (this.y + vy <= ys) {
          vy = ys - this.y; // snap
          arriveY = true;
        }
      }
     
    }
    else if (this.y == ys) {
      arriveY = true;
    }
   
    this.move(vx, vy);
   
    return (arriveX && arriveY);
  }
 
  /**
   * Sets this sprite coordinate to specified location on the background.
   */
  public void setLocation(double xs, double ys) {
    this.oldX = this.x = xs;
    this.oldY = this.y = ys;
  }
 
  /**
   * Moves this sprite as far as delta x (dx) and delta y (dy).
   */
  public void move(double dx, double dy) {
    if (dx != 0 || dy != 0) {
      this.oldX = this.x;
      this.x += dx;
      this.oldY = this.y;
      this.y += dy;
    }
   
    // if (dx != 0) {
    // oldX = x;
    // x += dx;
    // }
    //
    // if (dy != 0) {
    // oldY = y;
    // y += dy;
    // }
  }
 
  /**
   * Moves sprite <code>x</code> coordinate as far as delta x (dx).
   */
  public void moveX(double dx) {
    if (dx != 0) {
      this.oldX = this.x;
      this.x += dx;
    }
  }
 
  /**
   * Moves sprite <code>y</code> coordinate as far as delta y (dy).
   */
  public void moveY(double dy) {
    if (dy != 0) {
      this.oldY = this.y;
      this.y += dy;
    }
  }
 
  /**
   * Sets sprite <code>x</code> coordinate.
   */
  public void setX(double xs) {
    this.oldX = this.x = xs;
  }
 
  /**
   * Sets sprite <code>y</code> coordinate.
   */
  public void setY(double ys) {
    this.oldY = this.y = ys;
  }
 
  /**
   * Forces sprite <code>x</code> position to specified coordinate.
   * <p>
   *
   * The difference between {@link #setX(double)} with this method : <br>
   * <code>setX(double)</code> changes the sprite old position (oldX = xs),
   * while using <code>forceX(double)</code> <b>the old position is n ot
   * changed</b>.
   * <p>
   *
   * This method is used on collision check to move the sprite, but still keep
   * the sprite old position value.
   */
  public void forceX(double xs) {
    this.x = xs;
  }
 
  /**
   * Forces sprite <code>y</code> position to specified coordinate.
   * <p>
   *
   * The difference between {@link #setY(double)} with this method : <br>
   * <code>setY(double)</code> changes the sprite old position (oldY = ys),
   * while using <code>forceY(double)</code> <b>the old position is n ot
   * changed</b>.
   * <p>
   *
   * This method is used on collision check to move the sprite, but still keep
   * the sprite old position value.
   */
  public void forceY(double ys) {
    this.y = ys;
  }
 
  /**
   * Returns sprite <code>x</code> coordinate.
   */
  public double getX() {
    return this.x;
  }
 
  /**
   * Returns sprite <code>y</code> coordinate.
   */
  public double getY() {
    return this.y;
  }
 
  /**
   * Returns sprite <code>x</code> coordinate before the sprite moving to
   * the current position.
   */
  public double getOldX() {
    return this.oldX;
  }
 
  /**
   * Returns sprite <code>y</code> coordinate before the sprite moving to
   * the current position.
   */
  public double getOldY() {
    return this.oldY;
  }
 
  /** ************************************************************************* */
  /** ************************* SPEED VARIABLES ******************************* */
  /** ************************************************************************* */
 
  /**
   * Sets the speed of this sprite, the speed is based on actual time in
   * milliseconds, 1 means the sprite is moving as far as 1000 (1x1000ms)
   * pixels in a second. Negative value (-1) means the sprite moving backward.
   */
  public void setSpeed(double vx, double vy) {
    this.horizontalSpeed = vx;
    this.verticalSpeed = vy;
  }
 
  /**
   * Sets horizontal speed of the sprite, the speed is based on actual time in
   * milliseconds, 1 means the sprite is moving as far as 1000 (1x1000ms)
   * pixels in a second to the right, while negative value (-1) means the
   * sprite is moving to the left.
   */
  public void setHorizontalSpeed(double vx) {
    this.horizontalSpeed = vx;
  }
 
  /**
   * Sets vertical speed of the sprite, the speed is based on actual time in
   * milliseconds, 1 means the sprite is moving as far as 1000 (1x1000ms)
   * pixels in a second to the bottom, while negative value (-1) means the
   * sprite is moving to the top.
   */
  public void setVerticalSpeed(double vy) {
    this.verticalSpeed = vy;
  }
 
  /**
   * Moves sprite with specified angle, and speed.
   * <p>
   *
   * The angle is as followed:
   *
   * <pre>
   *   0�   : moving to top (12 o'clock)
   *   90�  : moving to right (3 o'clock)
   *   180� : moving to bottom (6 o'clock)
   *   270� : moving to left (9 o'clock)
   * </pre>
   */
  public void setMovement(double speed, double angleDir) {
    // convert degrees to radians
    double radians = Math.toRadians(angleDir);
   
    this.setSpeed(Math.sin(radians) * speed, -Math.cos(radians) * speed);
  }
 
  /**
   * Accelerates sprite horizontal speed by <code>accel</code> and limit the
   * speed to <code>maxSpeed</code>.
   * <p>
   *
   * This is used to create momentum speed (slowly increase/decrease the
   * sprite speed).
   * <p>
   *
   * For example :
   *
   * <pre>
   * Sprite s;
   *
   * public void update(long elapsedTime) {
   *   // accelerate sprite speed by 0.002
   *   // and limit the maximum speed to 4
   *   // moving right
   *   s.addHorizontalSpeed(elapsedTime, 0.002, 4);
   *   // moving left
   *   s.addHorizontalSpeed(elapsedTime, -0.002, -4);
   *   // momentum stop
   *   s.addHorizontalSpeed(elapsedTime, (s.getHorizontalSpeed() &gt; 0) ? -0.002
   *           : 0.002, 0);
   * }
   * </pre>
   */
  public void addHorizontalSpeed(long elapsedTime, double accel, double maxSpeed) {
    if (accel == 0 || elapsedTime == 0) {
      return;
    }
   
    this.horizontalSpeed += accel * elapsedTime;
   
    if (accel < 0) {
      if (this.horizontalSpeed < maxSpeed) {
        this.horizontalSpeed = maxSpeed;
      }
     
    }
    else {
      if (this.horizontalSpeed > maxSpeed) {
        this.horizontalSpeed = maxSpeed;
      }
    }
  }
 
  /**
   * Accelerates sprite vertical speed by <code>accel</code> and limit the
   * speed to <code>maxSpeed</code>.
   * <p>
   *
   * This is used to create momentum speed (slowly increase/decrease the
   * sprite speed).
   * <p>
   *
   * For example :
   *
   * <pre>
   * Sprite s;
   *
   * public void update(long elapsedTime) {
   *   // accelerate sprite speed by 0.002
   *   // and limit the maximum speed to 4
   *   // moving down
   *   s.addVerticalSpeed(elapsedTime, 0.002, 4);
   *   // moving up
   *   s.addVerticalSpeed(elapsedTime, -0.002, -4);
   *   // momentum stop
   *   s.addVerticalSpeed(elapsedTime,
   *           (s.getVerticalSpeed() &gt; 0) ? -0.002 : 0.002, 0);
   * }
   * </pre>
   */
  public void addVerticalSpeed(long elapsedTime, double accel, double maxSpeed) {
    if (accel == 0 || elapsedTime == 0) {
      return;
    }
   
    this.verticalSpeed += accel * elapsedTime;
   
    if (accel < 0) {
      if (this.verticalSpeed < maxSpeed) {
        this.verticalSpeed = maxSpeed;
      }
     
    }
    else {
      if (this.verticalSpeed > maxSpeed) {
        this.verticalSpeed = maxSpeed;
      }
    }
  }
 
  /**
   * Returns horizontal speed of the sprite.
   * <p>
   *
   * Positive means the sprite is moving to right, and negative means the
   * sprite is moving to left.
   */
  public double getHorizontalSpeed() {
    return this.horizontalSpeed;
  }
 
  /**
   * Returns vertical speed of the sprite.
   * <p>
   *
   * Positive means the sprite is moving to bottom, and negative means the
   * sprite is moving to top.
   */
  public double getVerticalSpeed() {
    return this.verticalSpeed;
  }
 
  /** ************************************************************************* */
  /** ******************* OTHER SPRITE POSITION FUNCTIONS ********************* */
  /** ************************************************************************* */
 
  /**
   * Returns sprite <code>x</code> coordinate relative to screen area.
   */
  public double getScreenX() {
    return this.x - this.background.getX() + this.background.getClip().x;
  }
 
  /**
   * Returns sprite <code>y</code> coordinate relative to screen area.
   */
  public double getScreenY() {
    return this.y - this.background.getY() + this.background.getClip().y;
  }
 
  /**
   * Returns the center of the sprite in <code>x</code> coordinate (x +
   * (width/2)).
   */
  public double getCenterX() {
    return this.x + (this.width / 2);
  }
 
  /**
   * Returns the center of the sprite in <code>y</code> coordinate (y +
   * (height/2)).
   */
  public double getCenterY() {
    return this.y + (this.height / 2);
  }
 
  /**
   * Returns whether the screen is still on background screen area in
   * specified offset.
   */
  public boolean isOnScreen(int leftOffset, int topOffset, int rightOffset, int bottomOffset) {
    Sprite.screenX = this.x - this.background.getX();
    Sprite.screenY = this.y - this.background.getY();
   
    return (Sprite.screenX + this.width > -leftOffset
            && Sprite.screenY + this.height > -topOffset
            && Sprite.screenX < this.background.getClip().width
                    + rightOffset && Sprite.screenY < this.background
            .getClip().height
            + bottomOffset);
  }
 
  /**
   * Returns whether the screen is still on background screen area.
   */
  public boolean isOnScreen() {
    return this.isOnScreen(0, 0, 0, 0);
  }
 
  /** ************************************************************************* */
  /** ************************* UPDATE SPRITE ********************************* */
  /** ************************************************************************* */
 
  /**
   * Updates this sprite.
   */
  public void update(long elapsedTime) {
    this.updateMovement(elapsedTime);
  }
 
  /**
   * Updates sprite movement.
   */
  protected void updateMovement(long elapsedTime) {
    this.move(this.horizontalSpeed * elapsedTime, this.verticalSpeed
            * elapsedTime);
  }
 
  /** ************************************************************************* */
  /** ************************* RENDER SPRITE ********************************* */
  /** ************************************************************************* */
 
  /**
   * Renders this sprite to specified graphics context.
   *
   * @param g graphics context
   */
  public void render(Graphics2D g) {
    Sprite.screenX = this.x - this.background.getX();
    Sprite.screenY = this.y - this.background.getY();
   
    // check whether the sprite is still on screen rendering area
    if (Sprite.screenX + this.width <= 0
            || Sprite.screenY + this.height <= 0
            || Sprite.screenX > this.background.getClip().width
            || Sprite.screenY > this.background.getClip().height) {
      return;
    }
   
    Sprite.screenX += this.background.getClip().x;
    Sprite.screenY += this.background.getClip().y;
   
    this.render(g, (int) Sprite.screenX, (int) Sprite.screenY);
  }
 
  /**
   * Renders sprite image to specified graphics context and specified
   * location.
   *
   * @param g graphics context
   * @param x screen x-coordinate
   * @param y screen y-coordinate
   */
  public void render(Graphics2D g, int x, int y) {
    g.drawImage(this.image, x, y, null);
  }
 
  /** ************************************************************************* */
  /** ************************** SPRITE FLAGS ********************************* */
  /** ************************************************************************* */
 
  /**
   * Returns sprite ID, ID is used to mark a sprite from other sprite.
   */
  public int getID() {
    return this.id;
  }
 
  /**
   * Sets sprite ID, ID is used to mark a sprite from other sprite.
   */
  public void setID(int id) {
    this.id = id;
  }
 
  /**
   * Returns sprite data ID, ID is used to mark a sprite from other sprite.
   */
  public Object getDataID() {
    return this.dataID;
  }
 
  /**
   * Sets sprite data ID, ID is used to mark a sprite from other sprite.
   */
  public void setDataID(Object dataID) {
    this.dataID = dataID;
  }
 
  /**
   * Returns the layer of this sprite.
   *
   * @see #setLayer(int)
   */
  public int getLayer() {
    return this.layer;
  }
 
  /**
   * Sets the layer of this sprite.
   * <p>
   *
   * Layer is used for z-order rendering. Use this along with
   * {@link PlayField#setComparator(Comparator)} or
   * {@link SpriteGroup#setComparator(Comparator)} for that purpose.
   *
   * @see #getLayer()
   */
  public void setLayer(int i) {
    this.layer = i;
  }
 
  /**
   * Returns active state of this sprite.
   */
  public boolean isActive() {
    return this.active;
  }
 
  /**
   * Sets active state of this sprite, only active sprite will be updated and
   * rendered and check for collision.
   * <p>
   *
   * Inactive sprite is same as dead sprite, it won't be updated nor rendered,
   * and only wait to be disposed (if the sprite is not immutable).
   * <p>
   *
   * The proper way to remove a sprite from the game, is by setting sprite
   * active state to false (Sprite.setActive(false)).
   *
   * @see #setImmutable(boolean)
   */
  public void setActive(boolean b) {
    this.active = b;
  }
 
  /**
   * Returns whether this sprite is immutable or not.
   */
  public boolean isImmutable() {
    return this.immutable;
  }
 
  /**
   * Sets immutable state of this sprite, immutable sprite means the sprite
   * won't be removed from its group even though the sprite is not active.
   * <p>
   *
   * This state is used for optimization by reusing inactive sprite rather
   * than making new sprite each time.
   * <p>
   *
   * Usually used for many, small, short live, and frequently used sprites
   * such as projectile in shooter game. Thus rather than making a new sprite
   * for every projectile that can cause performance degrade, the inactive
   * projectiles can be reuse again and again.
   * <p>
   *
   * <b>WARNING:</b> Immutable sprite won't be disposed by Java garbage
   * collector until the sprite is manually removed from its group using
   * {@link com.golden.gamedev.object.SpriteGroup#removeImmutableSprites()}.
   *
   * @see com.golden.gamedev.object.SpriteGroup#getInactiveSprite()
   * @see com.golden.gamedev.object.SpriteGroup#removeImmutableSprites()
   * @see #setActive(boolean)
   */
  public void setImmutable(boolean b) {
    this.immutable = true;
  }
 
  /**
   * Returns the distance of this sprite from the specified sprite.
   * <p>
   *
   * Used this method to check whether the specified sprite is in this sprite
   * range area or not.
   * <p>
   *
   * This method can be used for :
   * <ul>
   * <li>Determining sprite attack range.</li>
   * <li>Sprite aura that affecting surrounding unit.</li>
   * <li>Activating this sprite to chase player whenever the player come
   * closer to certain distance of this sprite.</li>
   * </ul>
   */
  public double getDistance(Sprite other) {
    return Math.sqrt(Math.pow(this.getCenterX() - other.getCenterX(), 2)
            + Math.pow(this.getCenterY() - other.getCenterY(), 2));
  }
 
  // private static int garbagecount = 0;
  // protected void finalize() throws Throwable {
  // System.out.println("Total sprite garbaged = " + (++garbagecount) + " = "
  // + this);
  // super.finalize();
  // }
 
}
TOP

Related Classes of com.golden.gamedev.object.Sprite

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.