/**
* tiger注:
*
* 这是网上看到的一个sprite类。
*
* spriteImg源图是帧图片的集合图片。按从上到下从左到右的排列。详见方法:splitImage
* 判断两个sprite是否碰撞的方法是collidesWith 。根据参数会判断是否像素碰撞。
* 像素碰撞验证的时候是看交叉的像素是否都不是透明!
*
* 我感觉这里有一个方法有误:getBounds(Sprite) . 应该按帧的大小来创建。
*
*/
/**
* @author Captain Awesome (http://www.javagaming.org/index.php?action=profile;u=28320)
* You may use this sprite-class (or parts of it) in any way you want, as long
* as you don't remove this notice and give me credit for my work.
*
* The only thing I didn't make myself is the splitImage(); method,
* which I found (and copied) from: http://www.javalobby.org/articles/ultimate-image/#13
*
* The reference to the BufferedImage you use in the constructor is not kept
* since the Sprite creates it's own optimized BufferedImage.
*
*/
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsEnvironment;
import java.awt.Image;
import java.awt.Rectangle;
import java.awt.Toolkit;
import java.awt.Transparency;
import java.awt.image.BufferedImage;
import java.awt.image.FilteredImageSource;
import java.awt.image.ImageFilter;
import java.awt.image.ImageProducer;
import java.awt.image.PixelGrabber;
import java.awt.image.RGBImageFilter;
import java.io.Serializable;
import java.util.logging.Level;
import java.util.logging.Logger;
public class Sprite implements Cloneable, Serializable {
public Sprite(BufferedImage img) {
spriteImg = toCompatibleImage(img);
}
@Override
public Sprite clone() {
try {
return (Sprite) super.clone();
} catch (CloneNotSupportedException e) {
System.out.println("Clone failed.");
return null;
}
}
/*
* Starts the animation of the sprite. The user must then call
* continueAnimation() in order to animate the sprite.
*/
public void setAnimation(int sleep) {
sleepTime = sleep;
currentSleepFrame = 0;
runAnim = true;
}
public boolean isAnimating() {
return runAnim;
}
/*
* Stops the animation
*/
public void stopAnimation() {
runAnim = false;
}
/*
* Paints the sprite. If splitSprite has been used, it will paint the
* current frame.
*/
public void paint(Graphics g) {
g.drawImage(this.getImage(), this.getRealX(), this.getRealY(), null);
}
// Continues an animation
public void continueAnimation() {
if (isAnimating() && currentSleepFrame >= sleepTime) {
currentSleepFrame = 0;
this.nextFrame();
} else {
currentSleepFrame++;
}
}
/*
* Paints the original Sprite if setAnimation has been used
*/
public void paintOrig(Graphics g) {
g.drawImage(spriteImg, x, y, null);
}
/*
* Sets the position based on the parameters
*/
public void setPosition(int x, int y) {
this.x = x;
this.y = y;
}
// Defines which reference pixel (i.e where the image will be placed on the
// x/y coordinates)
public void setRefPixel(int x, int y) {
refX = x;
refY = y;
}
/*
* Creates an animation of the current sprite, @param cols & rows decides
* how many columnss and rows the sprite should be split into It then
* modifies the @var frameSequence to hold every spritenumber in animImg
*/
public void splitSprite(int cols, int rows) {
this.cols = cols;
this.rows = rows;
animImg = splitImage(spriteImg, cols, rows);
frameSequence = new int[animImg.length];
for (int i = 0; i < animImg.length; i++)
frameSequence[i] = i;
}
public void setFrame(int frame) {
currentFrame = frame;
}
/*
* Edits the framesequence (alters the animation), 2 versions The first sets
* a name to use with getFrameSequence(); The other one just sets the
* framesequence
*/
public void setFrameSequence(int[] sequence, String name) {
frameSequence = sequence;
currentFrame = 0;
frameSequenceName = name;
}
public void setFrameSequence(int[] sequence) {
frameSequence = sequence;
currentFrame = 0;
frameSequenceName = "UNDEFINED";
}
public String getFrameSequence() {
return frameSequenceName;
}
public int[] getFrames() {
return this.frameSequence;
}
/*
* Goes to the next frame in the animation
*/
public void nextFrame() {
currentFrame++;
if (currentFrame >= frameSequence.length)
currentFrame = 0;
}
public int getFrame() {
return currentFrame;
}
public int getSize() {
if (animImg != null)
return animImg.length;
else
return 1;
}
/*
* Splits the image to create an animation
*/
private static BufferedImage[] splitImage(BufferedImage img, int cols,
int rows) {
int w = img.getWidth() / cols;
int h = img.getHeight() / rows;
int num = 0;
BufferedImage imgs[] = new BufferedImage[cols * rows];
for (int y = 0; y < rows; y++) {
for (int x = 0; x < cols; x++) {
if (num == imgs.length)
break;
imgs[num] = createCompatibleImage(w, h);
// Tell the graphics to draw only one block of the image
Graphics2D g = imgs[num].createGraphics();
g.drawImage(img, 0, 0, w, h, w * x, h * y, w * x + w,
h * y + h, null);
g.dispose();
num++;
}
}
return imgs;
}
// Creates a BufferedImage that is optimized for this system.
private static BufferedImage createCompatibleImage(int width, int height) {
GraphicsConfiguration gfx = GraphicsEnvironment
.getLocalGraphicsEnvironment().getDefaultScreenDevice()
.getDefaultConfiguration();
return gfx.createCompatibleImage(width, height,
Transparency.TRANSLUCENT);
}
private static BufferedImage toCompatibleImage(BufferedImage image) {
// Create a new compatible image
BufferedImage bimg = createCompatibleImage(image.getWidth(), image
.getHeight());
// Get the graphics of the image and paint the original image onto it.
Graphics2D g = (Graphics2D) bimg.getGraphics();
g.drawImage(image, 0, 0, null);
g.dispose();
// Return the new, compatible image.
return bimg;
}
/*
* Collision detection between the current sprite and another sprite
*/
public boolean collidesWith(Sprite otherSprite, boolean pixelPerfect) {
boolean isColliding = false;
Rectangle r1 = getBounds(this);
Rectangle r2 = getBounds(otherSprite);
if (r1.intersects(r2)) {
if (pixelPerfect) {
isColliding = pixelPerfectCollision(otherSprite, r1, r2);
} else {
isColliding = true;
}
}
return isColliding;
}
/*
* pixelPerfectCollision(); first determines the area where the sprites
* collides AKA the collision-rectangle. It then grabs the pixels from both
* sprites which are inside the rectangle. It then checks every pixel from
* the arrays given by grabPixels();, and if 2 pixels at the same position
* are opaque, (alpha value over 0) it will return true. Otherwise it will
* return false.
*/
private boolean pixelPerfectCollision(Sprite sprite, Rectangle r1,
Rectangle r2) {
int cornerTopX = -1;
int cornerTopY = -1;
int cornerBottomX = 1;
int cornerBottomY = 1;
/*
* Get the X-values for the two coordinates where the sprites collide
* Seriously, don't use the for loop, I don't know what I was thinking.
* Solution found below.
*/
// for(int i=0;i<r1.getWidth();i++) {
// if(r1.getX()+i >= r2.getX() & r1.getX()+i < r2.getX()+r2.getWidth())
// {
// if(cornerTopX==-1)cornerTopX = (int) (r1.getX() + i);
// cornerBottomX = (int) (r1.getX() + i);
// }
// }
cornerTopX = (r1.x > r2.x) ? r1.x : r2.x;
cornerBottomX = ((r1.x + r1.width) < (r2.x + r2.width)) ? (r1.x + r1.width)
: (r2.x + r2.width);
/*
* Get the Y-values for the two coordinates where the sprites collide
* Solution found below.
*/
// for(int i=0;i<r1.getHeight();i++) {
// if(r1.getY()+i >= r2.getY() & r1.getY()+i < r2.getY()+r2.getHeight())
// {
// if(cornerTopY==-1)cornerTopY = (int) (r1.getY() + i);
// cornerBottomY = (int) (r1.getY() + i);
// }
// }
cornerTopY = (r1.y > r2.y) ? r1.y : r2.y;
cornerBottomY = ((r1.y + r1.height) < (r2.y + r2.height)) ? (r1.y + r1.height)
: (r2.y + r2.height);
// Determine the width and height of the collision rectangle
int width = cornerBottomX - cornerTopX;
int height = cornerBottomY - cornerTopY;
// Create arrays to hold the pixels
int[] pixels1 = new int[width * height];
int[] pixels2 = new int[width * height];
// Create the pixelgrabber and fill the arrays
PixelGrabber pg1 = new PixelGrabber(getImage(),
cornerTopX - getRealX(), cornerTopY - getRealY(), width,
height, pixels1, 0, width);
PixelGrabber pg2 = new PixelGrabber(sprite.getImage(), cornerTopX
- sprite.getRealX(), cornerTopY - sprite.getRealY(), width,
height, pixels2, 0, width);
// Grab the pixels
try {
pg1.grabPixels();
pg2.grabPixels();
} catch (InterruptedException ex) {
Logger.getLogger(Sprite.class.getName())
.log(Level.SEVERE, null, ex);
}
// Check if pixels at the same spot from both arrays are not
// transparent.
for (int i = 0; i < pixels1.length; i++) {
int a = (pixels1[i] >>> 24) & 0xff;
int a2 = (pixels2[i] >>> 24) & 0xff;
/*
* Awesome, we found two pixels in the same spot that aren't
* completely transparent! Thus the sprites are colliding!
*/
if (a > 0 && a2 > 0)
return true;
}
return false;
}
// Invokes transparency on the selected color
public void invokeTransparency(Color color) {
spriteImg = makeTransparent(spriteImg, color);
if (this.cols > 0 & this.rows > 0)
this.splitSprite(this.cols, this.rows);
}
public void invokeTransparency(Color color, int newAlphaValue) {
spriteImg = makeTransparent(spriteImg, color, newAlphaValue);
if (this.cols > 0 & this.rows > 0)
this.splitSprite(this.cols, this.rows);
}
public static BufferedImage makeTransparent(BufferedImage img,
final Color color) {
ImageFilter filter = new RGBImageFilter() {
public int markerRGB = color.getRGB() | 0xFF000000;
@Override
public final int filterRGB(int x, int y, int rgb) {
if ((rgb | 0xFF000000) == markerRGB)
return 0x00FFFFFF & rgb;
else
return rgb;
}
};
ImageProducer ip = new FilteredImageSource(img.getSource(), filter);
Image temp = Toolkit.getDefaultToolkit().createImage(ip);
BufferedImage bufImg = createCompatibleImage(img.getWidth(), img
.getHeight());
Graphics2D g = bufImg.createGraphics();
g.drawImage(temp, 0, 0, null);
g.dispose();
return bufImg;
}
public static BufferedImage makeTransparent(BufferedImage img,
final Color color, final int newColor) {
ImageFilter filter = new RGBImageFilter() {
public int markerRGB = color.getRGB() | 0xFF000000;
@Override
public final int filterRGB(int x, int y, int rgb) {
if ((rgb | 0xFF000000) == markerRGB) {
return newColor & rgb;
} else {
return rgb;
}
}
};
ImageProducer ip = new FilteredImageSource(img.getSource(), filter);
Image temp = Toolkit.getDefaultToolkit().createImage(ip);
BufferedImage bufImg = createCompatibleImage(img.getWidth(), img
.getHeight());
Graphics2D g = bufImg.createGraphics();
g.drawImage(temp, 0, 0, null);
g.dispose();
return bufImg;
}
/*
* Returns the width of the current sprite
*/
public int getWidth() {
return this.getImage().getWidth();
}
/*
* Returns the height of the sprite
*/
public int getHeight() {
return this.getImage().getHeight();
}
/*
* Returns the X-position of the sprite getRealX() returns the X-Position of
* the Sprite's upper-left corner
*/
public int getX() {
return x;
}
public int getRefX() {
return refX;
}
public int getRealX() {
return x - refX;
}
/*
* Returns the Y-position of the sprite getRealY() returns the Y-position of
* the Sprite's upper-left corner
*/
public int getY() {
return y;
}
public int getRefY() {
return refY;
}
public int getRealY() {
return y - refY;
}
/*
* Returns the boundaries for the sprite, used for collision detection
*/
public static Rectangle getBounds(Sprite sprite) {
return new Rectangle(sprite.getRealX(), sprite.getRealY(), sprite
.getWidth(), sprite.getHeight());
}
/*
* Returns the image this sprite is using (if it was split, it will return
* the current frame. Else it will return the whole image.)
*/
public BufferedImage getImage() {
if (animImg != null && currentFrame < frameSequence.length)
return animImg[frameSequence[currentFrame]];
else
return spriteImg;
}
// Returns the whole image, no matter if it has been split or not.
public BufferedImage getOrigImage() {
return spriteImg;
}
// Flips the sprite (horizontal/vertical)
public void flipHorizontal() {
int w = this.getOrigImage().getWidth();
int h = this.getOrigImage().getHeight();
BufferedImage bimg = new BufferedImage(w, h,
BufferedImage.TYPE_4BYTE_ABGR);
Graphics2D g = bimg.createGraphics();
g.drawImage(this.getOrigImage(), 0, 0, w, h, w, 0, 0, h, null);
g.dispose();
this.spriteImg = toCompatibleImage(bimg);
if (this.rows > 0 & this.cols > 0)
animImg = splitImage(spriteImg, cols, rows);
}
public void flipVertical() {
int w = this.getOrigImage().getWidth();
int h = this.getOrigImage().getHeight();
BufferedImage bimg = new BufferedImage(w, h,
BufferedImage.TYPE_4BYTE_ABGR);
Graphics2D g = bimg.createGraphics();
g.drawImage(this.getOrigImage(), 0, 0, w, h, 0, h, w, 0, null);
g.dispose();
this.spriteImg = toCompatibleImage(bimg);
if (this.rows > 0 & this.cols > 0)
animImg = splitImage(spriteImg, cols, rows);
}
// Call one of these methods after the sprite has been de-serialized.
public void reloadSprite(BufferedImage img) {
this.spriteImg = img;
if (this.rows > 0 & this.cols > 0)
animImg = splitImage(spriteImg, cols, rows);
}
// Getting the cols and rows
public int getCols() {
return this.cols;
}
public int getRows() {
return this.rows;
}
public static BufferedImage duplicateAndReverse(BufferedImage bimg) {
BufferedImage temp = createCompatibleImage(bimg.getWidth() * 2, bimg
.getHeight());
int w = bimg.getWidth();
int h = bimg.getHeight();
Graphics2D g = temp.createGraphics();
g.drawImage(bimg, 0, 0, null);
g.drawImage(bimg, w, 0, w * 2, h, w, 0, 0, h, null);
g.dispose();
return temp;
}
private transient BufferedImage spriteImg;
private transient BufferedImage[] animImg;
private int x;
private int y;
private int refX;
private int refY;
private int frameSequence[] = { 0 };
private int currentFrame = 0;
private int sleepTime;
private int currentSleepFrame;
private boolean runAnim = false;
private int cols = 0;
private int rows = 0;
private String frameSequenceName = "ORIG";
// For serialization
private static final long serialVersionUID = 1L;
}