Java Swing Timer and Animation: how to put it toge

2019-01-09 17:25发布

问题:

I am gonna re-post this questione again trying to be more precise and hoping I will get some help because this is driving me crazy. I am developing a board game with up to 6 player, each one with a different colored pawn. I have the following image that is loaded in BufferedImage arrays treating it as a sprite:

and this is the relative code, putting each face of each colored die in a position in the BufferedImage[]:

private BufferedImage[] initAnimationBuffer() {
    BufferedImage[] result = new BufferedImage[36];
    for (int i = 0; i < 6; i++) {
        for (int j = i; j < 6 + i; j++)
            result[i + j] = DieSprite.getSprite(j, i, 0);

    }

    return result;
}

Then each player, according to his color, wil have also the following matrix containing the faces of his color according to the obtained die value/position. In other words this matrix contains "a line" of the image and it is indexed by value:

private BufferedImage[][] initExactDieFaces() {
    BufferedImage[][] result = new BufferedImage[6][1];
    int row = -1;
    String myColor = this.coreGame.getMyPartecipant().getColor();
    if (myColor.equals(Constants.COLOR[0])) {
        row = 0;
    } else if (myColor.equals(Constants.COLOR[1])) {
        row = 2;
    } else if (myColor.equals(Constants.COLOR[2])) {
        row = 4;
    } else if (myColor.equals(Constants.COLOR[3])) {
        row = 1;
    } else if (myColor.equals(Constants.COLOR[4])) {
        row = 5;
    } else if (myColor.equals(Constants.COLOR[5])) {
        row = 3;
    }
    int offset = 0;
    for (int i = 0; i < 6; i++) {
        result[i][0] = DieSprite.getSprite(row, i, offset);
        offset += 2;
    }
    return result;
}

What I want is the following: -when the "flip die" button is pressed, I want that (for example) 20 random die faces are shown (they should be taken from the first array, AnimationBuffer) in a specific JLabel inside a JPanel -as soon as the previous animation has finished, I want that the obtained result of the launch of the die is shown (according to the color pawn, taken from ExcatDieFaces).

To get this I know that I need Swing Timer but I am not able to put it all together; here's some code of the startAnimationDie method which is called when the "flip die" button is pressed:

private void startAnimationDie(final JPanel dieContainer) {

  final BufferedImage[] animationBuffer = initAnimationBuffer();
  final BufferedImage[][] exactDieFaces = initExactDieFaces();
  final AnimationSprite animation = new AnimationSprite(
                    animationBuffer, Constants.DIE_ANIMATION_SPEED);

  /* getting launch value fromt the core Game */
  int launchResult = coreGame.launchDie();
  coreGame.getMyPartecipant().setLastLaunch(launchResult);

  final Timer timer = new Timer(250, new ActionListener() {

  @Override
  public void actionPerformed(ActionEvent e) {

     dieContainer.removeAll();
     dieContainer.updateUI();
     animation.start();
     JLabel resultDie = new JLabel();
     resultDie.setBounds(60, 265, Constants.DIE_SIZE,Constants.DIE_SIZE);
     resultDie.setIcon(new ImageIcon(animationBuffer[new Random().nextInt(36)]));
     dieContainer.add(resultDie);
     dieContainer.updateUI();
     updateUI();
     repaint();

    }
  });

/* animation begins, rolling faces are shown each time the Timer ends*/
for(int i = 0; i<20; i++) 
  timer.start()

/* showing the final face according to the pawn color and the obtained result from the launch */

dieContainer.removeAll();
dieContainer.updateUI();
AnimationSprite resultAnimation = new AnimationSprite(exactDieFaces[launchResult - 1], 6);
resultAnimation.start(); 
resultAnimation.update();
resultDie.setIcon(new ImageIcon(exactDieFaces[launchResult - 1][0]));
resultDie.setBounds(60, 265, Constants.DIE_SIZE, Constants.DIE_SIZE);
dieContainer.add(resultDie);
dieContainer.updateUI();
dieContainer.repaint();

}

How can I make it work? I think I am supposed to use Swing.invokeAndWait but I cannot put together all the pieces...Can you help please?

回答1:

  1. Don't call updateUI, unless you're dealing with installing a look and feel, it's not doing what you think it is (and it's very inefficient)
  2. Don't rebuild the UI each time, this is time consuming work, which is going to make the animation look stilled and staggered and probably flash a lot. Instead, simply update the icon property of the label
  3. Use a single Timer, allow it to increment a counter, so you know how many times it's been called and update the die roll and counter on each tick.

Think of the Timer as a kind of loop, where on each iteration (tick), you need to do something (like increment the counter)

(Note- When it looks like the die has "stalled", it's because the image is been displayed more then once in sequence. You could over come this by placing all the images into a List and using Collections.shuffle. Do this three times, adding the result to another List should give you 24, no-repeating sequence (ok, it "might" repeat on the boundaries, but it's better then using Math.random ;))

import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

public class Test {

    public static void main(String[] args) {
        new Test();
    }

    public Test() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                    ex.printStackTrace();
                }

                JFrame frame = new JFrame("Testing");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.add(new TestPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class TestPane extends JPanel {

        private BufferedImage[] dice = new BufferedImage[6];
        private JLabel die;

        public TestPane() {
            try {
                BufferedImage img = ImageIO.read(new File("/Users/swhitehead/Documents/Die.png"));
                int width = 377 / 6;
                for (int index = 0; index < 6; index++) {
                    dice[index] = img.getSubimage(width * index, 0, width, width);
                }
            } catch (IOException ex) {
                ex.printStackTrace();
            }
            setLayout(new GridBagLayout());
            GridBagConstraints gbc = new GridBagConstraints();
            gbc.gridwidth = GridBagConstraints.REMAINDER;
            die = new JLabel(new ImageIcon(dice[0]));
            add(die, gbc);

            JButton roll = new JButton("Roll");
            add(roll, gbc);

            roll.addActionListener(new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    roll.setEnabled(false);
                    Timer timer = new Timer(250, new ActionListener() {
                        private int counter;
                        private int lastRoll;
                        @Override
                        public void actionPerformed(ActionEvent e) {
                            if (counter < 20) {
                                counter++;
                                lastRoll = (int)(Math.random() * 6);
                                System.out.println(counter + "/" + lastRoll);
                                die.setIcon(new ImageIcon(dice[lastRoll]));
                            } else {
                                lastDieRollWas(lastRoll);
                                ((Timer)e.getSource()).stop();
                                roll.setEnabled(true);
                            }
                        }
                    });
                    timer.start();
                }
            });
        }

        protected void lastDieRollWas(int roll) {
            System.out.println("You rolled " + (roll + 1));
        }

        @Override
        public Dimension getPreferredSize() {
            return new Dimension(200, 200);
        }

    }

}