Partial drawing of image
Let's begin by recalling our sprite image.The image is actually 128x128 pixels in size. And it is evenly divided into 16 regions. Each region is a partial image of 32x32 pixels. That composes 4 animation frames for each of the four moving directions.
By default, the drawImage() method of a Graphics2D object will draw the whole image. You may think of a Graphics2D object as a drawing board. The drawImage() API is in fact very flexible. It allows us to select partial region of the source image to be drawn.
Compare the following examples :
- gr.drawImage(spriteImage,50,50,this);
- gr.drawImage(spriteImage,50,50,82,82,0,0,32,32,this);
gr.drawImage(spriteImage,dx1,dy1,dx2,dy2,sx1,sy1,sx2,sy2,imageObserver);where (dx1,dy1,dx2,dy2) is the bounding rectangle in the destination (the drawing board)
(sx1,sy1,sx2,sy2) is the bounding rectangle of the source image.
Let's write a simple example to show the two drawImage() methods.
We just need to change the paintComponent() method in TransparentImage.java in the previous article.
// override the paint() method public void paintComponent(java.awt.Graphics gr) { // draw background gr.drawImage(backgroundImage,0,0,this); // draw whole image (128x128 pixels) gr.drawImage(spriteImage,50,200,this); // draw a partial image (32x32 pixels) int dx1=100,dy1=100,dx2=dx1+32,dy2=dx1+32; // target position int sx1=0,sy1=0,sx2=sx1+32,sy2=sy1+32; // source region gr.drawImage(spriteImage,dx1,dy1,dx2,dy2,sx1,sy1,sx2,sy2,this); }
And here comes the result.
Defining the source regions
The original source image actually contains 16 regions. For each directions there are four gestures for the moving sprite. We could pre-define those regions in advance using a 3 dimensional array.
int[][][] bunnyRect = // [direction][gesture][x,y,w,h] { {{ 0, 0,32,32}, {32, 0,32,32}, {64, 0,32,32}, {96, 0,32,32} }, // left {{ 0,32,32,32}, {32,32,32,32}, {64,32,32,32}, {96,32,32,32} }, // right {{ 0,64,32,32}, {32,64,32,32}, {64,64,32,32}, {96,64,32,32} }, // down {{ 0,96,32,32}, {32,96,32,32}, {64,96,32,32}, {96,96,32,32} } // up };
In the animation loop, we just need to select the correct region according to the moving direction and the animation gesture. The gesture number will be incremented while the bunny is moving. And it goes back to zero when gesture>3.
Running Bunny
In part 2 we have a bouncing ball moving in the sky, the following sample added a running bunny on the ground.
/****************************************************************************** * File : MovingSprite.java * Author : http://java.macteki.com/ * Description : * A ball flying in the sky and a bunny running on the ground * required background.jpg and bunny_sprite.png * Tested with : JDK 1.6 ******************************************************************************/ import java.awt.image.BufferedImage; class MovingSprite extends javax.swing.JPanel implements Runnable { // image object for double buffering BufferedImage drawingBoard=new BufferedImage(300,300,BufferedImage.TYPE_INT_RGB); // image object for holding the background JPEG BufferedImage backgroundImage; BufferedImage bunnySprite; int xBall=100, yBall=100; // initial coordinates of the moving ball int xVelocity=3; // moving 3 pixels per frame (to the right) // initial coordinates and velocity of the bunny sprite int xBunny=50, yBunny=250; int xvBunny=2; public MovingSprite() throws Exception { int w=drawingBoard.getWidth(this); int h=drawingBoard.getHeight(this); this.setPreferredSize(new java.awt.Dimension(w,h)); // read background image String jpeg_file="background.jpg"; backgroundImage = javax.imageio.ImageIO.read(new java.io.File(jpeg_file)); // read the sprite image BufferedImage tmp = javax.imageio.ImageIO.read(new java.io.File("bunny_sprite.png")); bunnySprite = makeTransparent(tmp); } private BufferedImage makeTransparent(BufferedImage tmpImage) { int h=tmpImage.getHeight(null); int w=tmpImage.getWidth(null); BufferedImage resultImage=new BufferedImage(w,h,BufferedImage.TYPE_INT_ARGB); // assume the upperleft corner of the original image is a transparent pixel int transparentColor=tmpImage.getRGB(0,0); for (int y=0;y<h;y++) for (int x=0;x<w;x++) { int color=tmpImage.getRGB(x,y); if (color==transparentColor) color=color & 0x00FFFFFF; // clear the alpha flag resultImage.setRGB(x,y,color); } return resultImage; } // override the paint() method, we don't count on the system. // We draw our own panel. public void paintComponent(java.awt.Graphics gr) { // Redraw the whole image instead of redrawing every object in the screen. // This technique is commonly known as "double buffering" gr.drawImage(drawingBoard,0,0,this); } // start the bouncing thread public void start() { new Thread(this).start(); } // thread entry point public void run() { try { while (true) { redrawBackground(); // redraw the background moveBall(); // move the ball to a new position moveSprite(); // move the bunny sprite repaint(); // redraw the panel. Thread.sleep(30); // delay for persistence of vision. } } catch (Exception e) { e.printStackTrace(); } } // redraw the background image. // this effectively erase all sprites. public void redrawBackground() { java.awt.Graphics2D gr=(java.awt.Graphics2D) drawingBoard.getGraphics(); gr.drawImage(backgroundImage,0,0,this); } public void moveBall() { int diameter=10; // diameter of the ball // update ball position xBall+=xVelocity; if (xBall>=300-diameter || xBall<0) // hit border { xBall-=xVelocity; // undo movement xVelocity=-xVelocity; // change direction of velocity } // draw ball at new position java.awt.Graphics2D gr = (java.awt.Graphics2D) drawingBoard.getGraphics(); gr.setColor(java.awt.Color.RED); // this is the ball color java.awt.geom.Ellipse2D newCircle= new java.awt.geom.Ellipse2D.Double(xBall,yBall,diameter,diameter); gr.fill(newCircle); } // Yes, it is very ugly to declare variable here. // Don't worry, everything will be moved to a Sprite class soon. int animationFrame=0; // incremented for every frame. int gesture=0; // animation gesture (0-3) int relativeSpeed=2; // update position at every 2 frames (moving half slower) int gestureWait=8; // update animation gesture at every 8 frames // pre-defined source regions int[][][] bunnyRect = // [direction][gesture][x,y,w,h] { {{ 0, 0,32,32}, {32, 0,32,32}, {64, 0,32,32}, {96, 0,32,32} }, // left {{ 0,32,32,32}, {32,32,32,32}, {64,32,32,32}, {96,32,32,32} }, // right {{ 0,64,32,32}, {32,64,32,32}, {64,64,32,32}, {96,64,32,32} }, // down {{ 0,96,32,32}, {32,96,32,32}, {64,96,32,32}, {96,96,32,32} } // up }; public void moveSprite() { animationFrame++; if (animationFrame % relativeSpeed==0) // control when to update the position { // move bunny to new position xBunny+=xvBunny; if (xBunny>=300-32 || xBunny<0) // hit border { xBunny-=xvBunny; // undo movement xvBunny=-xvBunny; // change direction of velocity } } if (animationFrame % gestureWait==0) // control when to update gesture { gesture=(gesture+1)%4; } // draw bunny at new position java.awt.Graphics2D gr = (java.awt.Graphics2D) drawingBoard.getGraphics(); int w=32, h=32; int dx2=xBunny+w; int dy2=yBunny+h; int direction; if (xvBunny>0) direction=1; else direction=0; int[] srcBounds=bunnyRect[direction][gesture]; int sx1=srcBounds[0], sy1=srcBounds[1], sx2=sx1+32, sy2=sy1+32; gr.drawImage(bunnySprite,xBunny,yBunny,dx2,dy2,sx1,sy1,sx2,sy2,this); } public static void main(String[] args) throws Exception { javax.swing.JFrame window = new javax.swing.JFrame(); window.setDefaultCloseOperation(javax.swing.JFrame.EXIT_ON_CLOSE); window.setTitle("Macteki moving sprite"); MovingSprite spritePanel=new MovingSprite(); window.add(spritePanel); window.pack(); window.setVisible(true); spritePanel.start(); } }
Acknowledgement
- bunny_sprite.png is provided by Moosader :
http://moosader.deviantart.com/art/Public-domain-bunny-sprite-151156167
- background.jpg is provided by Jesccia Crabtree
http://www.jessicacrabtree.com/journal1/public-domain-nature-photos
Next
We will have a sprite manager that handle thousands of sprites in the next section.Part 5 completed. To be continued...
No comments:
Post a Comment