I am trying to make a ball gradually move when you press one of the arrow keys, right now it kind of just teleports. I want it so that you can see it move. Based on this example, I am using key bindings, and there is a variable called delta that causes the ball move by 50 pixels, but like I said the ball just appears 50 pixels in whichever direction the arrow key is you pressed, I want it to be like if you were to kick a ball you can see it get from point a to b. Go to line 89 that is where I think the problem is.
package game;
import java.awt.BasicStroke;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.AbstractAction;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.KeyStroke;
/**
* @see https://stackoverflow.com/questions/6991648
* @see https://stackoverflow.com/questions/6887296
* @see https://stackoverflow.com/questions/5797965
*/
public class LinePanel extends JPanel {
myObject ball;
private Point b1 = new Point(0,0);
private MouseHandler mouseHandler = new MouseHandler();
private Point p1 = new Point(100, 100);
private Point p2 = new Point(540, 380);
private boolean drawing;
public LinePanel() {
this.setPreferredSize(new Dimension(640, 480));
this.addMouseListener(mouseHandler);
this.addMouseMotionListener(mouseHandler);
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
g2d.setColor(Color.blue);
g2d.setRenderingHint(
RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setStroke(new BasicStroke(8,
BasicStroke.CAP_ROUND, BasicStroke.JOIN_BEVEL));
g.drawLine(p1.x, p1.y, p2.x, p2.y);
ball = new myObject(b1.x,b1.y,"Stuff/ball.png",50,50);
g.drawImage(ball.getImage(),ball.getX(),ball.getY(), ball.getWidth(), ball.getHeight(), null);
repaint();
}
private class MouseHandler extends MouseAdapter {
@Override
public void mousePressed(MouseEvent e) {
drawing = true;
p1 = e.getPoint();
p2 = p1;
repaint();
}
@Override
public void mouseReleased(MouseEvent e) {
drawing = false;
p2 = e.getPoint();
repaint();
}
@Override
public void mouseDragged(MouseEvent e) {
if (drawing) {
p2 = e.getPoint();
repaint();
}
}
}
private class ControlPanel extends JPanel {
private static final int DELTA = 50;
// above is telling the ball to move by 50 pixels
// I want it to move by 50 pixels but gradually I dont want it to teleport
public ControlPanel() {
this.add(new MoveButton("\u2190", KeyEvent.VK_LEFT, -DELTA, 0));
this.add(new MoveButton("\u2191", KeyEvent.VK_UP, 0, -DELTA));
this.add(new MoveButton("\u2192", KeyEvent.VK_RIGHT, DELTA, 0));
this.add(new MoveButton("\u2193", KeyEvent.VK_DOWN, 0, DELTA));
}
private class MoveButton extends JButton {
KeyStroke k;
int myX, myY;
public MoveButton(String name, int code, final int myX, final int myY) {
super(name);
this.k = KeyStroke.getKeyStroke(code, 0);
this.myX = myX;
this.myY = myY;
this.setAction(new AbstractAction(this.getText()) {
@Override
public void actionPerformed(ActionEvent e) {
LinePanel.this.b1.translate(myX, myY);
LinePanel.this.repaint();
}
});
ControlPanel.this.getInputMap(
WHEN_IN_FOCUSED_WINDOW).put(k, k.toString());
ControlPanel.this.getActionMap().put(k.toString(), new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
MoveButton.this.doClick();
}
});
}
}
}
private void display() {
JFrame f = new JFrame("LinePanel");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(this);
f.add(new ControlPanel(), BorderLayout.SOUTH);
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
new LinePanel().display();
}
});
}
}
The basic premise for any animation is change over time.
You need to be able to move the ball from position A to position B over a given time period. To do that, you need some kind of "ticker" that can be used to update the position of the ball over that period. Generally speaking, 25fps (or about 40 milliseconds) is more than enough.
To achieve this safely in Swing, the easiest solution is to use a Swing Timer
. You could use a Thread
, but then you become responsible for syncing the updates back to the UI and it's more complexity then is really required at this stage.
This example uses a duration of 1 second to move the ball from point A to point B. This is a linear animation and you would need to investigate an appropriate animation framework to get a more complex solution.
import java.awt.BasicStroke;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.AbstractAction;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.KeyStroke;
import javax.swing.Timer;
public class LinePanel extends JPanel {
// myObject ball;
private Point b1 = new Point(0, 0);
private Point startPoint = new Point(0, 0);
private Point targetPoint = new Point(0, 0);
private MouseHandler mouseHandler = new MouseHandler();
private Point p1 = new Point(100, 100);
private Point p2 = new Point(540, 380);
private boolean drawing;
private Timer animate;
private long startTime;
private int duration = 1000;
public LinePanel() {
this.setPreferredSize(new Dimension(640, 480));
this.addMouseListener(mouseHandler);
this.addMouseMotionListener(mouseHandler);
animate = new Timer(40, new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
long now = System.currentTimeMillis();
long dif = now - startTime;
if (dif >= duration) {
dif = duration;
((Timer)e.getSource()).stop();
}
float progress = (float)dif / (float)duration;
b1 = calculateProgress(startPoint, targetPoint, progress);
repaint();
}
});
animate.setRepeats(true);
animate.setCoalesce(true);
}
public void moveBallTo(Point target) {
if (animate.isRunning()) {
animate.stop();
}
startPoint = b1;
targetPoint = target;
startTime = System.currentTimeMillis();
animate.start();
}
public void moveBallBy(int xDelta, int yDelta) {
animate.stop();
Point t = new Point(targetPoint == null ? b1 : targetPoint);
t.x += xDelta;
t.y += yDelta;
moveBallTo(t);
}
public Point calculateProgress(Point startPoint, Point targetPoint, double progress) {
Point point = new Point();
if (startPoint != null && targetPoint != null) {
point.x = calculateProgress(startPoint.x, targetPoint.x, progress);
point.y = calculateProgress(startPoint.y, targetPoint.y, progress);
}
return point;
}
public int calculateProgress(int startValue, int endValue, double fraction) {
int value = 0;
int distance = endValue - startValue;
value = (int)Math.round((double)distance * fraction);
value += startValue;
return value;
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
g2d.setColor(Color.blue);
g2d.setRenderingHint(
RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setStroke(new BasicStroke(8,
BasicStroke.CAP_ROUND, BasicStroke.JOIN_BEVEL));
g.drawLine(p1.x, p1.y, p2.x, p2.y);
g.setColor(Color.RED);
g.drawOval(b1.x - 4, b1.y - 4, 8, 8);
// ball = new myObject(b1.x, b1.y, "Stuff/ball.png", 50, 50);
// g.drawImage(ball.getImage(), ball.getX(), ball.getY(), ball.getWidth(), ball.getHeight(), null);
repaint();
}
private class MouseHandler extends MouseAdapter {
@Override
public void mousePressed(MouseEvent e) {
drawing = true;
p1 = e.getPoint();
p2 = p1;
repaint();
}
@Override
public void mouseReleased(MouseEvent e) {
drawing = false;
p2 = e.getPoint();
repaint();
}
@Override
public void mouseDragged(MouseEvent e) {
if (drawing) {
p2 = e.getPoint();
repaint();
}
}
}
private class ControlPanel extends JPanel {
private static final int DELTA = 50;
// above is telling the ball to move by 50 pixels
// I want it to move by 50 pixels but gradually I dont want it to teleport
public ControlPanel() {
this.add(new MoveButton("\u2190", KeyEvent.VK_LEFT, -DELTA, 0));
this.add(new MoveButton("\u2191", KeyEvent.VK_UP, 0, -DELTA));
this.add(new MoveButton("\u2192", KeyEvent.VK_RIGHT, DELTA, 0));
this.add(new MoveButton("\u2193", KeyEvent.VK_DOWN, 0, DELTA));
}
private class MoveButton extends JButton {
KeyStroke k;
int myX, myY;
public MoveButton(String name, int code, final int myX, final int myY) {
super(name);
this.k = KeyStroke.getKeyStroke(code, 0);
this.myX = myX;
this.myY = myY;
this.setAction(new AbstractAction(this.getText()) {
@Override
public void actionPerformed(ActionEvent e) {
// LinePanel.this.b1.translate(myX, myY);
moveBallBy(myX, myY);
LinePanel.this.repaint();
}
});
ControlPanel.this.getInputMap(
WHEN_IN_FOCUSED_WINDOW).put(k, k.toString());
ControlPanel.this.getActionMap().put(k.toString(), new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
MoveButton.this.doClick();
}
});
}
}
}
private void display() {
JFrame f = new JFrame("LinePanel");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(this);
f.add(new ControlPanel(), BorderLayout.SOUTH);
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
new LinePanel().display();
}
});
}
}
Take a look at ...
- How to use Swing Timers
- Timing Framework
- Trident
- Universal Tween Engine
Side note
Doing this ball = new myObject(b1.x, b1.y, "Stuff/ball.png", 50, 50);
in you paint method is incredibly inefficient. paint
may be called a number of times in quick succession. Where ever possible, you want the paint
methods to be as optimized as you can make them, to much time spent in your paint method will make your program look slow
Lots of ways to do the code and I'm not familiar with the environment.
But basically you use the key stroke to set the direction of movement, and then you have another routine that moves the ball in that direction. How many pixels you move in one step an how long the step takes is your speed.
How you use the keyboard is down to the game play. Holding down accelerates, When going right pressing left stops it. Do you want to give it inertia. i.e going right to left slows motion to the right to zero then starts going left.
All sorts of options but the trick is make moving the ball one routine and then use your input device to control the direction , acceleration etc.