How To Remove The Last Image In An Animation

2019-01-15 11:51发布

问题:

What I am trying to do is create an animation that creates a 'running' motion. Whenever I draw it on the screen, the last frame in the animation is left behind (So there is a trail of animation frames left behind when the sprite moves). I've tried if statements and changing the image's draw position when the frame changes:

if(a2.sceneNum() == 0)
spectre_Draw1 = (screenWidth() / 2 - 120 / 2 + 120 - 6);
else
spectre_Draw1 = 0;

g.drawImage(pic[2], spectre_Draw1, (screenHeight() / 2 - 180 / 2), null);

if(a2.sceneNum() == 1)
spectre_Draw2 = (screenWidth() / 2 - 120 / 2 + 120 - 6);
else
spectre_Draw2 = 0;

g.drawImage(pic[3], spectre_Draw2, (screenHeight() / 2 - 180 / 2), null);

if(a2.sceneNum() == 2)
spectre_Draw3 = (screenWidth() / 2 - 120 / 2 + 120 - 6);
else
spectre_Draw3 = 0;

g.drawImage(pic[4], spectre_Draw3, (screenHeight() / 2 - 180 / 2), null);

Is there a way to do this while removing the trailing images?

回答1:

I know it says "Avoid responding to other answers" but as a new registrant (but long-time user) I am only eligible to write "answers" or suggest edits.. I can't add comments to an answer until I am upvoted ! :-)

However, there are just too many errors in the source code in peeskillet's answer to qualify simply as "suggested edits" :-)

  1. Accidental interchange / confusion of ROWS and COLUMNS :

        SPRITE_ROWS = 5;
        SPRITE_COLUMNS = 2;
    
        DIM_W = img.getWidth() / SPRITE_ROWS;
        DIM_H = img.getHeight() / SPRITE_COLUMNS;
    

Further down the code, the program is clearly trying to treat rows and columns of the animation pictures in the grid correctly, but it is only because both of these are backwards that the program works. (ie there are 5 Columns and 2 Rows in the grid of images for the sample nerdgirl).

  1. Accidental interchange of DIM_H and DIM_W (1 occurrence) :

                if (x1Src >= img.getWidth() - DIM_H - 5) {  // 5 to take care of precisi
    

    "DIM_H" should be "DIM_W"

As listed, this code would cause a premature jumping to the next row of animation images in the grid (not showing the last image in each row) in the case where the individual images are either significantly taller than they are wide, or else there are many images in each row of the original grid. With the nerdgirl sample (833 x 639), the last image in each row would not have been shown if the overall grid had been just 10 pixels taller. (DIM_H = 319, img.getWidth()-DIM_H-5 = 503 and the last frame shows when x1Src = 498 ..only just made it !)

  1. Current code as shown only processes 2 rows of animation frames (even thought it correctly calculates the SIZE of each frame by changing eg SPRITE_COLUMNS (sic) to 4 for the running Man example).

This is because the following if-test :

                    if (y1Src >= DIM_H - 5) { // 5 to take care of lack of prec
                        y1Src = 0;

... should be :

                    if (y1Src >= imag.getHeight() - DIM_H - 5) { // 5 to take car
                        y1Src = 0;

..as the current code only ever displays 2 rows of sprite images even if there were more. (This explains why the flapping bird sample works "ok" without showing the final all-white frame... look carefully and it only shows the first 2 rows of images).

  1. Use of hard-coded 5 for "loss of precision" (meaning when the source images have not been composed evenly into the combined grid and they are not an exact multiple of the frame size) should actually refer to the number of frames present :

                if (x1Src >= img.getWidth() - DIM_W - SPRITE_COLUMNS) {  // - SPRITE_COLUMNS to take care of non-aligned source images (see samples)
    
                if (y1Src >= img.getHeight() - DIM_H - SPRITE_ROWS) {  // - SPRITE_ROWS to take care of non-aligned source images (see samples)
    

This is because the possible "loss of precision" is no more than 1 per frame in each row and column - resulting from the integer rounding that occurs when the animation image width and height are calculated. eg The nerdgirl sample at 833 x 639 calculates an animation frame size of 166 x 319, so after 5 images we have shown up to 830,638 of the original image.

However, I'd suggest the following would make both those if-statements much clearer and simpler :

                if (x2Src > img.getWidth()) {     // Beyond end of Row

                if (y2Src > img.getHeight()) {    // Beyond Last Row

... and there are a few other little tweaks as shown below.

import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.Timer;

public class NerdGirl extends JPanel {
    private static int SPRITE_COLUMNS = 5;         // Number of columns of sprite images in source file
    private static int SPRITE_ROWS    = 2;         // Number of rows    of sprite images in source file
    private static final int DELAY    = 100;       // inter-frame pause in milliseconds

    private int dim_W;                             // Width of an individual animation frame
    private int dim_H;                             // Height of an individual animation frame

    private int x1Src;                             // top-left coordinates of current frame
    private int y1Src;
    private int x2Src;                             //bottom-right coordinates of current frame
    private int y2Src;

    private BufferedImage img;

    public NerdGirl() {
        try {
            img = ImageIO.read(getClass().getResource("images/nerdgirl.jpg"));
            SPRITE_ROWS = 2;
            // Other sample files
            //img = ImageIO.read(getClass().getResource("images/explosion.png"));
            //SPRITE_ROWS = 3;
            //img = ImageIO.read(getClass().getResource("images/birdflight.png"));
            //SPRITE_ROWS = 3;
            //img = ImageIO.read(getClass().getResource("images/running_man.png"));
            //SPRITE_ROWS    = 4;
            //SPRITE_COLUMNS = 6;

        } catch (IOException ex) {
            Logger.getLogger(NerdGirl.class.getName()).log(Level.SEVERE, null, ex);
        }
        dim_W = img.getWidth()  / SPRITE_ROWS;
        dim_H = img.getHeight() / SPRITE_COLUMNS;
        x1Src = 0;
        y1Src = 0;
        x2Src = x1Src + dim_W;
        y2Src = y1Src + dim_H;

        Timer timer = new Timer(DELAY, new ActionListener() {
            public void actionPerformed(ActionEvent e) {

                // Move right to next frame (next column)
                x1Src += dim_W;
                x2Src += dim_W;

                if (x2Src > img.getWidth()) {  // Past end of current row.
                    x1Src = 0;                  // Back to start of row
                    x2Src = dim_W;

                    y1Src += dim_H;             // Move down to next Row
                    y2Src += dim_H;
                    if (y2Src > img.getHeight()) { // Below bottom of source grid of images
                        y1Src = 0;              // Back to Top.
                        y2Src = dim_H;
                    }
                }

                repaint();
            }
        });
        timer.start();
    }

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        g.drawImage(img, 0, 0, getWidth(), getHeight(), x1Src, y1Src, x2Src, y2Src, this);
    }

    @Override
    public Dimension getPreferredSize() {
        return (img == null) ? new Dimension(300, 300) : new Dimension(dim_W, dim_H);
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                JFrame frame = new JFrame();
                frame.add(new NerdGirl());
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }
}
  1. Finally, please note that independent of these issues, the sample animation source grids are poorly put together (the sprite-frame alignment is not always consistent) so that in the samples on this page you will sometimes see pieces of the neighbouring frames showing. Watch the bird-frame animation above carefully and see explanatory images here and here.)

If the grids were all compiled as a whole number multiple of the individual frame-size (as is the case with the running man png image @ 900x600 - even though the sample provided has picture "overlap" - see image link above), then the program code for the coordinate-checking could be even easier :

        Timer timer = new Timer(DELAY, new ActionListener() {
            public void actionPerformed(ActionEvent e) {

                // Move right to next frame (next column)
                x1Src += dim_W;

                if (x1Src == img.getWidth()) {  // At end of current row.
                    x1Src = 0;                  // Go Back to start of row
                    y1Src += dim_H;             // Move down to next Row

                    if (y1Src == img.getHeight()) { // Past all images
                        y1Src = 0;              // Back to Top.
                    }
                }

                x2Src += dim_W;
                y2Src += dim_H;

                repaint();
            }
        });
        timer.start();
    }

However, as input samples grids are unreliable in overall size and composition, the earlier code is recommended as it will cope with these inconsistencies.

I hope my answer saves some others valuable time in the long run !

Cheers,



回答2:

Note: Code below in example program has some logic errors. Please see answer from Warren K for explanation and fixes

I noticed that you're trying to use different images for the animation image. You know you could use a single animation sprite (given it is formatted in a grid like pattern) and just change the locations of the certain x y positions in the drawImage method

public abstract boolean drawImage(Image img,
            int dx1,
            int dy1,
            int dx2,
            int dy2,
            int sx1,
            int sy1,
            int sx2,
            int sy2,
            ImageObserver observer)
img - the specified image to be drawn. This method does nothing if img is null.
dx1 - the x coordinate of the first corner of the destination rectangle.
dy1 - the y coordinate of the first corner of the destination rectangle.
dx2 - the x coordinate of the second corner of the destination rectangle.
dy2 - the y coordinate of the second corner of the destination rectangle.
sx1 - the x coordinate of the first corner of the source rectangle.
sy1 - the y coordinate of the first corner of the source rectangle.
sx2 - the x coordinate of the second corner of the source rectangle.
sy2 - the y coordinate of the second corner of the source rectangle.
observer - object to be notified as more of the image is scaled and converted.

See full description API

That being said, You can use a javax.swing.Timer to animate and change the locations of the source image.

here are some examples using this same code for all the example you see below. I just changed the image and change the SPRITE_ROWS, SPRITE_COLUMNS, and DELAY accordingly. See more at How to Use Swing Times

import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.Timer;

public class NerdGirl extends JPanel {
    private static final int SPRITE_ROWS = 5;
    private static final int SPRITE_COLUMNS = 2;
    private static final int DELAY = 150;

    private int DIM_W;
    private int DIM_H;

    private int x1Src;
    private int y1Src;
    private int x2Src;
    private int y2Src;

    private BufferedImage img;

    public NerdGirl() {
        try {
            img = ImageIO.read(getClass().getResource("/resources/nerd-girl.jpg"));
        } catch (IOException ex) {
            Logger.getLogger(NerdGirl.class.getName()).log(Level.SEVERE, null, ex);
        }
        DIM_W = img.getWidth() / SPRITE_ROWS;
        DIM_H = img.getHeight() / SPRITE_COLUMNS;
        x1Src = 0;
        y1Src = 0;
        x2Src = x1Src + DIM_W;
        y2Src = y1Src + DIM_H;
        Timer timer = new Timer(DELAY, new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                if (x1Src >= img.getWidth() - DIM_H - 5) {  // 5 to take care of precision loss
                    x1Src = 0;
                    x2Src = x1Src + DIM_W;
                    if (y1Src >= DIM_H - 5) { // 5 to take care of precision loss
                        y1Src = 0;
                        y2Src = y1Src + DIM_H;
                    } else {
                        y1Src += DIM_H;
                        y2Src = y1Src + DIM_H;
                    }

                } else {
                    x1Src += DIM_W;
                    x2Src = x1Src + DIM_W;
                }

                repaint();
            }
        });
        timer.start();
    }

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        g.drawImage(img, 0, 0, getWidth(), getHeight(), x1Src, y1Src, x2Src, y2Src, this);
    }

    @Override
    public Dimension getPreferredSize() {
        return (img == null) ? new Dimension(300, 300) : new Dimension(DIM_W, DIM_H);
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                JFrame frame = new JFrame();
                frame.add(new NerdGirl());
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }
}