I'm trying to create a simple panel where a 2-dimensional ball is bouncing up and down. I can't get it to work because for some reason I can't call the repaint method more than once a second. The design is basically that there is an object that can be given "an impulse" with the method move(). Everytime the evaluatePosition method is called, the current position will be calculated through the time that has passed, the velocity and the acceleration. The code for the panel is:
public class Display extends JPanel {
private MovableObject object = new MovableObject(new Ellipse2D.Double(5,5,50,50));
private static final int DELAY = 1000;
public Display(){
object.move(50,50);
Timer timer = new Timer(DELAY, new ActionListener(){
#Override
public void actionPerformed(ActionEvent e){
object.evaluatePosition();
repaint();
}
});
timer.start();
}
}
#Override
public void paintComponent(Graphics g){
super.paintComponent(g);
Graphics2D g2 = (Graphics2D)g;
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g.drawOval((int)object.getPosition().getX(), (int)object.getPosition.getY()
(int)object.getShape().getWidth(), object.getShape().getHeight());
}
This code works for DELAY=1000 but not for DELAY=100 or DELAY=10 and so on. I read some example code here on SO but they all seem to me like what I already did. So why is my code not working?
EDIT (2016-01-30):
Since it really seems to be a performance issue, here's the code for the MovableObject (I just thought it would be irrelevant and you will probably see why):
public class MovableObject {
// I would really like to use Shape instead of Ellipse2D so that
// objects of any shape can be created
private Ellipse2D.Double shape;
private Point position;
// Vector is my own class. I want to have some easy vector addition and
// multiplication and magnitude methods
private Vector velocity = new Vector(0, 0);
private Vector acceleration = new Vector(0, 0);
private Date lastEvaluation = new Date();
public MovableObject(Ellipse2D.Double objectShape){
shape = objectShape;
}
public void evaluatePosition(){
Date currentTime = new Date();
long deltaTInS = (currentTime.getTime()-lastEvaluation.getTime())/1000;
// s = s_0 + v*t + 0.5*a*t^2
position = new Point((int)position.getX()+ (int)(velocity.getX()*deltaTInS) + (int)(0.5*acceleration.getX()*deltaTInS*deltaTInS),
(int)position.getY()+ (int)(velocity.getY()*deltaTInS) + (int)(0.5*acceleration.getY()*deltaTInS*deltaTInS));
lastEvaluation = currentTime;
}
}
public void move(Vector vector){
velocity = velocity.add(vector);
evaluatePosition();
}
public Point getPosition(){
return position;
}
public Ellipse2D.Double getShape(){
return shape;
}
My move method does not change position but velocity. Please notice that I just changed the shape Object from Shape to Ellipse2D for testing if my code has a performance issue because of the additional code. So if you think this is more complex than it needs to be: I actually want to add some complexity so that the MovableObject can have the shape of any subclass of shape. I've seen a lot of code that seemed more complex to me and rendered fast. So I'd like to know what's wrong with this (besides the fact that it's a bit too complex for just rendering an ellipse).
Okay, so this is a simple example, based on the out-of-context code snippet you left which doesn't seem to have any problems. It has variable speed controlled by a simple slider...
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.AffineTransform;
import java.awt.geom.Ellipse2D;
import java.awt.geom.GeneralPath;
import java.awt.geom.PathIterator;
import java.util.Random;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JSlider;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
public class Display extends JPanel {
public static void main(String[] args) {
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 Display());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
private MovableObject object = new MovableObject(new Ellipse2D.Double(5, 5, 50, 50));
private int delay = 40;
private Timer timer = new Timer(40, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
object.evaluatePosition(getSize());
repaint();
}
});
private JSlider slider = new JSlider(5, 1000);
public Display() {
object.move(50, 50);
slider = new JSlider(5, 1000);
slider.setSnapToTicks(true);
slider.setMajorTickSpacing(10);
slider.setMinorTickSpacing(5);
setLayout(new BorderLayout());
add(slider, BorderLayout.SOUTH);
// This is simply designed to put an artificate delay between the
// change listener and the time the update takes place, the intention
// is to stop it from pausing the "main" timer...
Timer delay = new Timer(250, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
if (timer != null) {
timer.stop();
}
timer.setDelay(slider.getValue());
timer.start();
}
});
slider.addChangeListener(new ChangeListener() {
#Override
public void stateChanged(ChangeEvent e) {
delay.restart();
repaint();
}
});
slider.setValue(40);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g.create();
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2.draw(object.getTranslatedShape());
FontMetrics fm = g2.getFontMetrics();
String text = Integer.toString(slider.getValue());
g2.drawString(text, 0, fm.getAscent());
g2.dispose();
}
public class MovableObject {
private Shape shape;
private Point location;
private int xDelta, yDelta;
public MovableObject(Shape shape) {
this.shape = shape;
location = shape.getBounds().getLocation();
Random rnd = new Random();
xDelta = rnd.nextInt(8);
yDelta = rnd.nextInt(8);
if (rnd.nextBoolean()) {
xDelta *= -1;
}
if (rnd.nextBoolean()) {
yDelta *= -1;
}
}
public void move(int x, int y) {
location.setLocation(x, y);
}
public void evaluatePosition(Dimension bounds) {
int x = location.x + xDelta;
int y = location.y + yDelta;
if (x < 0) {
x = 0;
xDelta *= -1;
} else if (x + shape.getBounds().width > bounds.width) {
x = bounds.width - shape.getBounds().width;
xDelta *= -1;
}
if (y < 0) {
y = 0;
yDelta *= -1;
} else if (y + shape.getBounds().height > bounds.height) {
y = bounds.height - shape.getBounds().height;
yDelta *= -1;
}
location.setLocation(x, y);
}
public Shape getTranslatedShape() {
PathIterator pi = shape.getPathIterator(AffineTransform.getTranslateInstance(location.x, location.y));
GeneralPath path = new GeneralPath();
path.append(pi, true);
return path;
}
}
}
You could also have a look at
Swing animation running extremely slow
Rotating multiple images causing flickering. Java Graphics2D
Java Bouncing Ball
for some more examples...
Related
I'm trying to make a racing game with the top down view on a static player in the middle of the screen, so instead of moving the player through the map, the map would move around the player. Since it's a racing game, I wanted it to also be somewhat similar to a car, but I've been having trouble with rotating the map around the player and having that work with translations.
I've tried keeping track of the center by adding or subtracting from it, which is what I did for the translations, but it doesn't work with the rotate method. The rotate function wouldn't rotate about the player and instead would rotate the player around some other point, and the translations would snap to a different location from the rotations. I'm sure my approach is flawed, and I have read about layers and such, but I'm not sure what I can do with them or how to use them. Also, any recommendations as to how to use java graphics in general would be greatly appreciated!
This is what I have in my main:
import javax.swing.JFrame;
import java.awt.BorderLayout;
public class game
{
public static void main(String []args)
{
JFrame frame = new JFrame();
final int FRAME_WIDTH = 1000;
final int FRAME_HEIGHT = 600;
frame.setSize(FRAME_WIDTH, FRAME_HEIGHT);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
final Map b = new Map();
frame.add(b,BorderLayout.CENTER);
frame.setVisible(true);
b.startAnimation();
}
}
And this is the class that handles all the graphics
import java.awt.Graphics;
import java.awt.Graphics2D;
import javax.swing.JComponent;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
public class Map extends JComponent implements Runnable, KeyListener
{
private int speed = 5;
private int xcenter = 500; // starts on player
private int ycenter = 300;
private double angle = 0.0;
private int[] xcords = {xcenter+10, xcenter, xcenter+20};
private int[] ycords = {ycenter-10, ycenter+20, ycenter+20};
private boolean moveNorth = false;
private boolean moveEast = false;
private boolean moveSouth = false;
private boolean moveWest = false;
public Map()
{
addKeyListener(this);
setFocusable(true);
setFocusTraversalKeysEnabled(false);
}
public void startAnimation()
{
Thread t = new Thread(this);
t.start();
}
public void paintComponent(Graphics g)
{
g.fillPolygon(xcords, ycords, 3);
// move screen
if(moveNorth)
{
ycenter += speed;
g.translate(xcenter, ycenter);
}
else if(moveEast)
{
angle += ((1 * Math.PI/180) % (2 * Math.PI));
((Graphics2D) g).rotate(angle, 0, 0);
}
else if(moveSouth)
{
System.out.println(xcenter + ", " + ycenter);
ycenter -= speed;
((Graphics2D) g).rotate(angle, 0, 0);
g.translate(xcenter, ycenter);
}
else if(moveWest)
{
angle -= Math.toRadians(1) % (2 * Math.PI);
((Graphics2D) g).rotate(angle, 0, 0);
}
for(int i = -10; i < 21; i++)
{
g.drawLine(i * 50, -1000, i * 50, 1000);
g.drawLine(-1000, i * 50, 1000, i * 50);
}
g.drawOval(0, 0, 35, 35);
}
public void run()
{
while (true)
{
try
{
if(moveNorth || moveEast || moveSouth || moveWest)
{
repaint();
}
Thread.sleep(10);
}
catch (InterruptedException e)
{
}
}
}
public void keyPressed(KeyEvent e)
{
if(e.getExtendedKeyCode() == 68) // d
{
moveEast = true;
}
else if(e.getExtendedKeyCode() == 87) // w
{
moveNorth = true;
}
else if(e.getExtendedKeyCode() == 65) // a
{
moveWest = true;
}
else if(e.getExtendedKeyCode() == 83) // s
{
moveSouth = true;
}
}
public void keyReleased(KeyEvent e)
{
moveNorth = false;
moveEast = false;
moveSouth = false;
moveWest = false;
}
public void keyTyped(KeyEvent e)
{
}
}
You have to keep in mind that transformations are compounding, so if you rotate the Graphics context by 45 degrees, everything painted after it will be rotated 45 degrees (around the point of rotation), if you rotate it again by 45 degrees, everything painted after it will be rotated a total of 90 degrees.
If you want to paint additional content after a transformation, then you either need to undo the transformation, or, preferably, take a snapshot of the Graphics context and dispose of it (the snapshot) when you're done.
You also need to beware of the point of rotation, Graphics2D#rotate(double) will rotate the Graphics around the point of origin (ie 0x0), which may not be desirable. You can change this by either changing the origin point (ie translate) or using Graphics2D#rotate(double, double, double), which allows you to define the point of rotation.
For example...
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.event.KeyEvent;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.Set;
import java.util.TreeSet;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.ImageIO;
import javax.swing.AbstractAction;
import javax.swing.ActionMap;
import javax.swing.InputMap;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.KeyStroke;
import javax.swing.Timer;
public class Main {
public static void main(String[] args) {
new Main();
}
public Main() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
JFrame frame = new JFrame();
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
} catch (IOException ex) {
Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);
}
}
});
}
public class TestPane extends JPanel {
enum Direction {
LEFT, RIGHT;
}
protected enum InputAction {
PRESSED_LEFT, PRESSED_RIGHT, RELEASED_LEFT, RELEASED_RIGHT
}
private BufferedImage car;
private BufferedImage road;
private Set<Direction> directions = new TreeSet<>();
private double directionOfRotation = 0;
public TestPane() throws IOException {
car = ImageIO.read(getClass().getResource("/images/Car.png"));
road = ImageIO.read(getClass().getResource("/images/Road.png"));
InputMap im = getInputMap(WHEN_IN_FOCUSED_WINDOW);
ActionMap am = getActionMap();
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0, false), InputAction.PRESSED_LEFT);
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0, true), InputAction.RELEASED_LEFT);
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0, false), InputAction.PRESSED_RIGHT);
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0, true), InputAction.RELEASED_RIGHT);
am.put(InputAction.PRESSED_LEFT, new DirectionAction(Direction.LEFT, true));
am.put(InputAction.RELEASED_LEFT, new DirectionAction(Direction.LEFT, false));
am.put(InputAction.PRESSED_RIGHT, new DirectionAction(Direction.RIGHT, true));
am.put(InputAction.RELEASED_RIGHT, new DirectionAction(Direction.RIGHT, false));
Timer timer = new Timer(5, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
if (directions.contains(Direction.RIGHT)) {
directionOfRotation += 1;
} else if (directions.contains(Direction.LEFT)) {
directionOfRotation -= 1;
}
// No doughnuts for you :P
if (directionOfRotation > 180) {
directionOfRotation = 180;
} else if (directionOfRotation < -180) {
directionOfRotation = -180;
}
repaint();
}
});
timer.start();
}
protected void setDirectionActive(Direction direction, boolean active) {
if (active) {
directions.add(direction);
} else {
directions.remove(direction);
}
}
#Override
public Dimension getPreferredSize() {
return new Dimension(213, 216);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
drawRoadSurface(g2d);
drawCar(g2d);
g2d.dispose();
}
protected void drawCar(Graphics2D g2d) {
g2d = (Graphics2D) g2d.create();
int x = (getWidth() - car.getWidth()) / 2;
int y = (getHeight() - car.getHeight()) / 2;
g2d.drawImage(car, x, y, this);
g2d.dispose();
}
protected void drawRoadSurface(Graphics2D g2d) {
g2d = (Graphics2D) g2d.create();
// This sets the point of rotation at the center of the window
int midX = getWidth() / 2;
int midY = getHeight() / 2;
g2d.rotate(Math.toRadians(directionOfRotation), midX, midY);
// We then need to offset the top/left corner so that what
// we want draw appears to be in the center of the window,
// and thus will be rotated around it's center
int x = midX - (road.getWidth() / 2);
int y = midY - (road.getHeight() / 2);
g2d.drawImage(road, x, y, this);
g2d.dispose();
}
protected class DirectionAction extends AbstractAction {
private Direction direction;
private boolean active;
public DirectionAction(Direction direction, boolean active) {
this.direction = direction;
this.active = active;
}
#Override
public void actionPerformed(ActionEvent e) {
setDirectionActive(direction, active);
}
}
}
}
OK so i'm working on a school project (little animation) and I am currently trying to make rain. I'm not sure how I would go about drawing individual "drops" using JPanel. My Code so far:
Main Class:
public class RainPanel extends JPanel {
private static final long serialVersionUID = 1L;
public static void main(String[] args) {
new RainPanel();
}
private final int WIDTH = 800, HEIGHT = 800;
Drop drop;
public RainPanel() {
init();
}
public void init() {
JFrame frame = new JFrame("Rain");
JPanel drop = new Drop();
frame.setVisible(true);
frame.setSize(WIDTH, HEIGHT);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLocationRelativeTo(null);
frame.add(drop);
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
drop.paint(g);
}
Drop class:
public class Drop extends JPanel implements ActionListener{
private static final long serialVersionUID = 1L;
int x,y;
int yVel = 2;
Timer t = new Timer(5, this);
Random r = new Random();
ArrayList<Drop> DropArray;
public Drop() {
x = r.nextInt(800);
y = r.nextInt(800);
t.start();
}
public void paint(Graphics g) {
super.paintComponent(g);
DropArray = new ArrayList<>(100);
for (int i = 0; i < DropArray.size(); i++) {
DropArray.add(new Drop());
}
g.setColor(Color.BLUE);
g.fillRect(x, y, 3, 15);
}
public void update() {
y += yVel;
if (y > 800)
y = r.nextInt(800);
}
#Override
public void actionPerformed(ActionEvent e) {
update();
repaint();
}
I understand if you might be cringing hard right now (I'm fairly new to graphics coding and mostly familiar with Java itself). All i'm getting drawn currently is a single rain drop. Any suggestions are appreciated.
Don't call super.paintComponent from within paint, you're breaking the paint chain which could cause no end of issues. Override paintComponent directly instead
You shouldn't be modifying the state of a component or anything the component relies on from within any paint method, paint can be called a number of times in quick succession and this can cause no end of issues
Component based animation is not a simple task and unless you really, really need it, you should try and avoid it. Instead, write a class which is "paintable", which you can call from your paintComponent method
For example..
import java.awt.Color;
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.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class RainDropsKeepFalling {
public static void main(String[] args) {
new RainDropsKeepFalling();
}
public RainDropsKeepFalling() {
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 RainPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class RainPane extends JPanel {
private List<Drop> drops = new ArrayList<>(100);
public RainPane() {
for (int index = 0; index < 100; index++) {
drops.add(new Drop(getPreferredSize()));
}
Timer timer = new Timer(5, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
for (Drop drop : drops) {
drop.update(getSize());
repaint();
}
}
});
timer.start();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
protected void paintComponent(Graphics g) {
super.paintComponent(g);
for (Drop drop : drops) {
Graphics2D g2d = (Graphics2D) g.create();
drop.paint(g2d);
g2d.dispose();
}
}
}
protected static final Random random = new Random();
public static class Drop {
private double vDelta = random.nextDouble() + 0.5;
private int height = 15;
private int width = 3;
private double x;
private double y = -height;
private Rectangle2D shape;
public Drop(Dimension size) {
x = random.nextInt(size.width - width) + width;
y = random.nextInt(size.height - height) + height;
shape = new Rectangle2D.Double(x, y, width, height);
}
public void paint(Graphics2D g2d) {
g2d.setColor(Color.BLUE);
g2d.fill(shape);
}
public void update(Dimension size) {
y += vDelta;
if (y > size.height) {
y = -height;
x = random.nextInt(size.width - width) + width;
}
shape.setRect(x, y, width, height);
}
}
}
When I run the code, the added Example1 class to the JOptionPane (in Frame) should get keyInput and then change the y value of the player instance (in example1), but it doesn't work. Also, how would I be able to rotate the ship on its axis and then move in the direction its facing? Currently it moves in the direction its rotated towards, but it rotates on what seems the coordinates 0,0.
Frame
import javax.swing.*;
import java.awt.*;
/**
* Created by griffin on 12/7/2015.
*/
public class Frame extends JFrame {
public Frame() {
initUI();
}
private void initUI() {
JTabbedPane jtp = new JTabbedPane();
Example1 e1 = new Example1();
Example2 e2 = new Example2();
getContentPane().add(jtp);
jtp.add(e1);
jtp.add(e2);
jtp.addTab("Example 1", e1);
jtp.addTab("Example 2", e2);
setResizable(false);
pack();
setTitle("NLTP");
setLocationRelativeTo(null);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
JFrame frame = new Frame();
frame.setVisible(true);
}
});
}
}
Example1
import javax.swing.*;
import java.awt.*;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.geom.AffineTransform;
/**
* Created by griffin on 12/7/2015.
*/
public class Example1 extends JPanel implements Runnable {
private final int B_WIDTH = 640;
private final int B_HEIGHT = 480;
private int DELAY = 25;
private Thread thread;
Ship player = new Ship(320, 240);
public Example1() {
initScreen();
}
private void initScreen() {
JButton explanation = new JButton("Explanation");
explanation.setBounds(0, 0, 150, 30);
this.setLayout(null);
add(explanation);
addKeyListener(new keyInput());
setFocusable(true);
setBackground(Color.BLACK);
setPreferredSize(new Dimension(B_WIDTH, B_HEIGHT));
setDoubleBuffered(true);
}
#Override
public void addNotify() {
super.addNotify();
thread = new Thread(this);
thread.start();
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
draw(g);
}
public void draw(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
AffineTransform old = g2d.getTransform();
g2d.rotate(Math.toRadians(player.angle));
g2d.drawImage(player.ship, player.x, player.y, null);
g2d.setTransform(old);
Toolkit.getDefaultToolkit().sync();
}
public void cycle() {
player.y += player.vY;
}
#Override
public void run() {
long beforeTime, timeDiff, sleep;
beforeTime = System.currentTimeMillis();
while (true) {
cycle();
repaint();
timeDiff = System.currentTimeMillis() - beforeTime;
sleep = DELAY - timeDiff;
if (sleep < 0) {
sleep = 2;
}
try {
Thread.sleep(sleep);
} catch (InterruptedException e) {
System.out.println("Interrupted: " + e.getMessage());
}
beforeTime = System.currentTimeMillis();
}
}
public class keyInput extends KeyAdapter {
#Override
public void keyReleased(KeyEvent e) {
int key = e.getKeyCode();
if(key == KeyEvent.VK_W) {
}
}
#Override
public void keyTyped(KeyEvent e) {
}
#Override
public void keyPressed(KeyEvent e) {
int key = e.getKeyCode();
if (key == KeyEvent.VK_W) {
player.vY -= 1;
}
if(key == KeyEvent.VK_A) {
player.angle++;
}
if(key == KeyEvent.VK_D) {
player.angle--;
}
}
}
}
KeyListener is well known for having focus related issues. Not only does the component need to be focusable, but it has to have keyboard focus before it will register key events. This is an issue as it's all to easy for your component to lose focus for any number of reasons.
The solution is to use the key bindings API which was designed to help solve this issue. See How to Use Key Bindings for more details.
Swing uses a passive rendering approach. That is, updates occur when ever the API decides something needs to be updated. See Painting in AWT and Swing for more details
What you really need is some kind of active rendering approach, where updates are made on a regular bases. This can be accomplished in a verity of ways, the simplest would be to use a Swing Timer, as it's safe to update the UI (or values that the UI relies on) without the risk of introducing race conditions. See How to use Swing Timers for more details
So, this example basically abstracts the input into four basic movements, rotate left/right and up/down. The code doesn't care how those inputs are generated, only that they can be.
It then uses the key bindings API to register actions for the inputs. This is all done with a single Action class, which just adds the Input to a Set, which the Timer uses to determine what actions should be applied before requesting the UI be repainted.
The rotation is accomplished through the use of a AffineTransform. First we translate the origin to the sprites current x/y position, then rotate it around the center of the sprite. This reduces a lot of the complexity/issues related to rotating things (me simple)
AffineTransform at = AffineTransform.getTranslateInstance(xPos, yPos);
at.rotate(Math.toRadians(angle), sprite.getWidth() / 2, sprite.getHeight() / 2);
g2d.setTransform(at);
g2d.drawImage(sprite, 0, 0, this);
Just beware, a Graphics context is a shared resource, before making these type of changes, you need to make a copy of it (and dispose of it when you are finished)
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.HashSet;
import java.util.Set;
import javax.imageio.ImageIO;
import javax.swing.AbstractAction;
import javax.swing.ActionMap;
import javax.swing.InputMap;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.KeyStroke;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class FlyingPoniesWithGuns {
public static void main(String[] args) {
new FlyingPoniesWithGuns();
}
public FlyingPoniesWithGuns() {
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 enum Input {
ROTATE_LEFT,
ROTATE_RIGHT,
UP,
DOWN
}
public class TestPane extends JPanel {
private BufferedImage sprite;
private double angle;
private int xPos, yPos;
private double xDelta, yDelta;
private Set<Input> inputs;
public TestPane() throws IOException {
inputs = new HashSet<>(25);
sprite = ImageIO.read(getClass().getResource("/Pony.png"));
xPos = (400 - sprite.getWidth()) / 2;
yPos = (400 - sprite.getHeight()) / 2;
addKeyBinding("rotate-left", KeyEvent.VK_A, Input.ROTATE_LEFT);
addKeyBinding("rotate-right", KeyEvent.VK_D, Input.ROTATE_RIGHT);
Timer timer = new Timer(40, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
if (inputs.contains(Input.ROTATE_LEFT)) {
angle -= 5;
} else if (inputs.contains(Input.ROTATE_RIGHT)) {
angle += 5;
}
repaint();
}
});
timer.start();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(400, 400);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
g2d.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
g2d.setRenderingHint(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_ENABLE);
g2d.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
g2d.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
AffineTransform at = AffineTransform.getTranslateInstance(xPos, yPos);
at.rotate(Math.toRadians(angle), sprite.getWidth() / 2, sprite.getHeight() / 2);
g2d.setTransform(at);
g2d.drawImage(sprite, 0, 0, this);
g2d.dispose();
}
protected void addKeyBinding(String name, int keyCode, Input input) {
InputMap inputMap = getInputMap(WHEN_IN_FOCUSED_WINDOW);
ActionMap actionMap = getActionMap();
inputMap.put(KeyStroke.getKeyStroke(keyCode, 0, false), name + ".pressed");
actionMap.put(name + ".pressed", new InputAction(input, true));
inputMap.put(KeyStroke.getKeyStroke(keyCode, 0, true), name + ".released");
actionMap.put(name + ".released", new InputAction(input, false));
}
protected class InputAction extends AbstractAction {
private Input input;
private boolean pressed;
public InputAction(Input input, boolean pressed) {
this.input = input;
this.pressed = pressed;
}
#Override
public void actionPerformed(ActionEvent e) {
if (pressed) {
inputs.add(input);
} else {
inputs.remove(input);
}
}
}
}
}
Okay, what about movement? You could have a look at How do I make an entity move in a direction? and How can I move a sprite in the direction it is facing? for more ideas on how you might be able to achieve that
I decided to re-write my game using Swing's painting technique paintComponent() method(someone on SO actually told me to use this method). I decided to use JPanel as the game's base instead of Canvas. My previous written game uses a Canvas but the game could not show up on my 64 bit desktop but could show up on my 32 bit labtop which is why this game had to be re-written.
Problem now is, while the ship's movement works, the drawing seems awfully slow(unless it is my laptop problem?) compare to what I did before which was using AWT's double buffering drawing technique. I spend a whole day but could not figure out what could possibly make the ship run faster.
public class Ship extends JLabel implements KeyListener{
private Image image;
private boolean turnRight;
private int x;
private int y;
private int speed = 5;
private boolean turnLeft;
public Ship(int x, int y)
{
this.x = x;
this.y = y;
try {
image = ImageIO.read(new File("Ship/Ship.PNG"));
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
addKeyListener(this);
}
public Image getImage()
{
return image;
}
#Override
public void keyPressed(KeyEvent e) {
// TODO Auto-generated method stub
if(e.getKeyCode() == KeyEvent.VK_RIGHT)
{
x += speed;
setTurnRight(true);
setTurnLeft(false);
}
else if(e.getKeyCode() == KeyEvent.VK_LEFT)
{
x -= speed;
setTurnLeft(true);
setTurnRight(false);
}
// redraw yourself
repaint();
}
private void setTurnLeft(boolean turnLeft) {
// TODO Auto-generated method stub
this.turnLeft = turnLeft;
}
// swing custom painting
public void paintComponent(Graphics g)
{
if(x <= 0)
{
x = 0;
}
else if(x >= 610)
{
x = 610;
}
g.drawImage(getImage(), x, y, null);
}
public void setTurnRight(boolean turnRight)
{
this.turnRight = turnRight;
}
public boolean getTurnLeft()
{
return turnLeft;
}
public boolean getTurnRight()
{
return turnRight;
}
#Override
public void keyReleased(KeyEvent e) {
// TODO Auto-generated method stub
}
#Override
public void keyTyped(KeyEvent e) {
// TODO Auto-generated method stub
}
}
Normally, I would create a concept of a renderable element. I would maintain a list of these elements and update them accordingly within my main loop.
At the very least, each would have a concept of location, direction and rotation (if required), they would also be capable of been painted.
Within my main component, I would simply loop through all these elements and "draw" them, offset the Graphics context to allow for there position within the game space.
But that's not what you are doing...
Remember, components have a sense of location and size already, you shouldn't be trying to re-implement this requirement, instead, you should be finding ways to take advantage of it...(ie, don't maintain a reference to the x/y values ;))
The following is a simple example. It uses a JPanel to render the main image. The main loop (in this case a javax.swing.Timer), tells the component that it should update it's movement as required.
The ship itself is responding to key events by changing the rotation value by a given, variable, delta. This allows you to control the speed of the spin as you need (I've deliberately set it low to start with, so play around with it)
What you should resist doing, is changing the frame rate ;)
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.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.AbstractAction;
import javax.swing.ActionMap;
import javax.swing.InputMap;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.KeyStroke;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.border.LineBorder;
public class BattleShipGame {
public static void main(String[] args) {
new BattleShipGame();
}
public BattleShipGame() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(new OceanPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class OceanPane extends JPanel {
private BattleShip ship;
public OceanPane() {
setLayout(new GridBagLayout());
ship = new BattleShip();
add(ship);
Timer timer = new Timer(40, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
ship.move();
revalidate();
repaint();
}
});
timer.setRepeats(true);
timer.setCoalesce(true);
timer.start();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(400, 400);
}
}
public static class BattleShip extends JPanel {
protected static final int MAX_TURN_RATE = 5;
private BufferedImage ship;
private float angle;
private float angleDelta;
public BattleShip() {
setOpaque(false);
try {
ship = ImageIO.read(new File("BattleShip.png"));
} catch (IOException ex) {
ex.printStackTrace();
}
setFocusable(true);
InputMap im = getInputMap(WHEN_IN_FOCUSED_WINDOW);
ActionMap am = getActionMap();
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0), "leftTurn");
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0), "rightTurn");
am.put("leftTurn", new TurnAction(-0.1f));
am.put("rightTurn", new TurnAction(0.1f));
}
public void move() {
angle += angleDelta;
}
public void setAngle(float angle) {
this.angle = angle;
}
public float getAngle() {
return angle;
}
#Override
public Dimension getPreferredSize() {
Dimension size = new Dimension(0, 0);
if (ship != null) {
double rads = Math.toRadians(getAngle());
double sin = Math.abs(Math.sin(rads)), cos = Math.abs(Math.cos(rads));
int w = ship.getWidth();
int h = ship.getHeight();
size.width = (int) Math.floor(w * cos + h * sin);
size.height = (int) Math.floor(h * cos + w * sin);
}
return size;
}
#Override
public Dimension getMinimumSize() {
return getPreferredSize();
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (ship != null) {
Graphics2D g2d = (Graphics2D) g.create();
double rads = Math.toRadians(getAngle());
double sin = Math.abs(Math.sin(rads)), cos = Math.abs(Math.cos(rads));
int w = ship.getWidth();
int h = ship.getHeight();
int newWidth = (int) Math.floor(w * cos + h * sin);
int newHeight = (int) Math.floor(h * cos + w * sin);
AffineTransform at = new AffineTransform();
at.translate((newWidth - w) / 2, (newHeight - h) / 2);
at.rotate(Math.toRadians(getAngle()), w / 2, h / 2);
g2d.drawImage(ship, at, this);
g2d.dispose();
}
}
protected class TurnAction extends AbstractAction {
protected float delta;
public TurnAction(float delta) {
this.delta = delta;
}
#Override
public void actionPerformed(ActionEvent e) {
angleDelta += delta;
if (angleDelta > MAX_TURN_RATE) {
angleDelta = MAX_TURN_RATE;
} else if (angleDelta < (MAX_TURN_RATE * -1)) {
angleDelta = (MAX_TURN_RATE * -1);
}
}
}
}
}
I would recommend having a class which extends JPanel, using a javax.swing.Timer in there, defining your 1000/fps and your ActionListener, in which you use a repaint() which uses a paintComponent that you will make that would call upon the draw method in your Ship, which is now called paintComponent.
So, as that explaination was terrible, here is some code:
public class Class_Name extends JPanel()
{
Ship ship = new Ship(0,0);
javax.swing.Timer timer = new javax.swing.Timer(1000/60, new ActionListener(){
repaint();
});
public void paintComponent(Graphics g)
{
super.paintComponent();
ship.draw(g);
}
}
and the draw is, what is now called paintComponent.
If this didn't answer your question, please let me know.
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.