I am trying to move the blue square around the grid by pressing the arrow keys. I already set up the KeyListener
but when I repaint the frame, I have to move the frame around in order for it to update. How do I make it a smooth animation (moving the square from one coordinate to another)?
Example:
if (keyCode == KeyEvent.VK_LEFT){
x5=x5-xChange;
frame.repaint();
}
My images are buffered images and are all set to specific coordinates.
Here is my graphics class:
public static class myGraphicsPanel extends JPanel {
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawImage(block1,x1,y1,null);
g.drawImage(block2,x2,y2,null);
g.drawImage(block3,x3,y3,null);
g.drawImage(block4,x4,y4,null);
g.drawImage(block5,x5,y5,null);
g.drawImage(block6,x6,y6,null);
g.drawImage(block7,x7,y7,null);
g.drawImage(block8,x8,y8,null);
g.drawImage(blue,x9,y9,null);
}
You question is lacking a lot of information, how is the screen updated? How is the screen created? What relationship do the objects have with each other?
Many of these questions could be answered with a MCVE
Animation is the illusion of change over time. So you need a few things. You need some way to change the current state and some way to trigger and update to the UI over time.
The simplest (and safest) way to do this within Swing is to use a Swing Timer
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.Random;
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.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.Timer;
public class TestAnimation {
public static void main(String[] args) {
new TestAnimation();
}
public TestAnimation() {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
try {
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);
} catch (IOException ex) {
ex.printStackTrace();
}
}
});
}
public class TestPane extends JPanel {
private BufferedImage img;
private int xDelta, yDelta;
private int xPos, yPos;
public TestPane() throws IOException {
img = ImageIO.read(new File("/Users/shane/Dropbox/MegaTokyo/thumnails/2.jpg"));
Random rnd = new Random();
do {
xDelta = rnd.nextInt(4);
} while (xDelta == 0);
do {
yDelta = rnd.nextInt(4);
} while (yDelta == 0);
if (rnd.nextBoolean()) {
xDelta *= -1;
}
if (rnd.nextBoolean()) {
yDelta *= -1;
}
xPos = (getPreferredSize().width - img.getWidth()) / 2;
yPos = (getPreferredSize().height - img.getHeight()) / 2;
Timer timer = new Timer(40, new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
xPos += xDelta;
yPos += yDelta;
if (xPos + img.getWidth() > getWidth()) {
xPos = getWidth() - img.getWidth();
xDelta *= -1;
} else if (xPos < 0) {
xPos = 0;
xDelta *= -1;
}
if (yPos + img.getHeight() > getHeight()) {
yPos = getHeight() - img.getHeight();
yDelta *= -1;
} else if (yPos < 0) {
yPos = 0;
yDelta *= -1;
}
repaint();
}
});
timer.start();
}
@Override
public Dimension getPreferredSize() {
return new Dimension(400, 400);
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (img != null) {
Graphics2D g2d = (Graphics2D) g.create();
g2d.drawImage(img, xPos, yPos, this);
g2d.dispose();
}
}
}
}
Have a closer look at Concurrency in Swing and w to Use Swing Timers for more details
But, how do you do this using the key board? Surprisingly, it's very similar.
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.ActionMap;
import javax.swing.InputMap;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.KeyStroke;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class TestAnimation {
public static void main(String[] args) {
new TestAnimation();
}
public TestAnimation() {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
try {
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);
} catch (IOException ex) {
ex.printStackTrace();
}
}
});
}
public class TestPane extends JPanel {
private BufferedImage img;
private int xPos, yPos;
public TestPane() throws IOException {
img = ImageIO.read(new File("/Users/shane/Dropbox/MegaTokyo/thumnails/2.jpg"));
xPos = (getPreferredSize().width - img.getWidth()) / 2;
yPos = (getPreferredSize().height - img.getHeight()) / 2;
addKeyBinding("up", KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0), new KeyAction(0, -4));
addKeyBinding("down", KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0), new KeyAction(0, 4));
addKeyBinding("left", KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0), new KeyAction(-4, 0));
addKeyBinding("right", KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0), new KeyAction(4, 0));
}
protected void addKeyBinding(String name, KeyStroke keyStroke, Action action) {
InputMap im = getInputMap(WHEN_IN_FOCUSED_WINDOW);
ActionMap am = getActionMap();
im.put(keyStroke, name);
am.put(name, action);
}
@Override
public Dimension getPreferredSize() {
return new Dimension(400, 400);
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (img != null) {
Graphics2D g2d = (Graphics2D) g.create();
g2d.drawImage(img, xPos, yPos, this);
g2d.dispose();
}
}
public class KeyAction extends AbstractAction {
private int xDelta, yDelta;
public KeyAction(int xDelta, int yDelta) {
this.xDelta = xDelta;
this.yDelta = yDelta;
}
@Override
public void actionPerformed(ActionEvent e) {
xPos += xDelta;
yPos += yDelta;
if (xPos + img.getWidth() > getWidth()) {
xPos = getWidth() - img.getWidth();
} else if (xPos < 0) {
xPos = 0;
}
if (yPos + img.getHeight() > getHeight()) {
yPos = getHeight() - img.getHeight();
} else if (yPos < 0) {
yPos = 0;
}
repaint();
}
}
}
}
Have a look at How to use key bindings and How to use actions for more details
Try not to repaint the whole panel but only the area on which the square will be drawn, use
repaint(int x, int y , int width , int height);
where x and y will be the coordinates and width and height will width and height of your square.
You could give each of your BufferedImages a target variable. You would check to see if the current x position was greater than or less than the target variables, and make changes according the the value. You can do this with y coordinate too.
int x1 = 100;
int x2 = 100;
int xTarget1 = 200; // x target coordinate
int xTarget2 = 200;
// And so on, for as many BufferedImages
int xChange = 1;
// A loop, wherever it may be.
public void loop() {
if(x1 > xTarget1) {
x1 = x1 - xChange;
else if(x1 < xTarget1) {
x1 = x1 + xChange;
}
// Same for the other BufferedImages
}
And then you would draw it. This should create a more smooth move of your images. It will be a constant move unless you change the speed variable through the change. I hopes this helps.