Related
I have this code, that the oval shape should automatically move to the right/left/up/down (it depends on decision from the user) while implementing the runnable class. However it does not move. I would really appreciate any advice. Thank you in advance.
public class AnimationPanel extends JPanel implements Runnable{
public int getSpeed() {
return speed;
}
public void setSpeed(int speed) {
this.speed = speed;
}
public boolean isLeftToRight() {
return isLeftToRight;
}
public void setLeftToRight(boolean isLeftToRight) {
this.isLeftToRight = isLeftToRight;
}
private int x;
private int y;
private boolean isPaused = false;
private int width;
private int hwight;
private int speed=10;
private Thread animThread;
private boolean isLeftToRight = true;
public AnimationPanel() {
setBackground(Color.WHITE);
setDoubleBuffered(true);
x = 0;
y = 0;
animThread = new Thread(this);
animThread.start();
}
public void paint(Graphics g) {
super.paint(g);
Graphics2D g2 = (Graphics2D) g;
g2.setColor(Color.BLUE);
g2.fillOval(x, y,20,20);
}
public void movement() {
hwight = this.getHeight();
width = this.getWidth();
if(isLeftToRight) {
if(x<width) {
x=+10;
} else if(x>=0){
x=-10;
}
} else {
if(y<hwight) {
y=+10;
} else if(y>=0){
y=-10;
}
}
}
#Override
public void run() {
while(true) {
if (!isPaused)
{
movement();
repaint();
}
try {
Thread.sleep(speed);
} catch(InterruptedException e) {
JOptionPane.showMessageDialog(null, "Interrupted");
}
}
}
}
public class AppFrame extends JFrame implements ActionListener{
private AnimationPanel anim;
private JButton slowerButton;
private JButton fasterButton;
private JButton upDownButton;
private JButton leftRightButton;
private JPanel buttonsPanel;
private int height = 500;
private int width = 500;
public AppFrame() {
this.setTitle("Circle");
this.setSize(height,width);
this.setResizable(false);
initGui();
this.setLocationRelativeTo(null);
this.setDefaultCloseOperation(EXIT_ON_CLOSE);
}
private void initGui() {
buttonsPanel = new JPanel();
this.setLayout(new BorderLayout());
buttonsPanel.setLayout(new FlowLayout());
buttonsPanel.add(slowerButton = new JButton("Slower"));
buttonsPanel.add(fasterButton = new JButton("Faste"));
buttonsPanel.add(upDownButton = new JButton("up and down"));
buttonsPanel.add(leftRightButton = new JButton("left to right"));
slowerButton.addActionListener(this);
fasterButton.addActionListener(this);
upDownButton.addActionListener(this);
leftRightButton.addActionListener(this);
anim = new AnimationPanel();
this.add(buttonsPanel, BorderLayout.PAGE_START);
this.add(anim);
}
#Override
public void actionPerformed(ActionEvent e) {
Object zrodlo = e.getSource();
if(slowerButton == zrodlo) {
anim.setSpeed(anim.getSpeed()*2);
} else if(fasterButton == zrodlo) {
anim.setSpeed(anim.getSpeed()/2);
} else if(upDownButton == zrodlo) {
anim.setLeftToRight(false);
} else if(leftRightButton == zrodlo) {
anim.setLeftToRight(true);
}
}
}
public class Runner {
public static void main(String[] args) {
AppFrame app =new AppFrame();
app.setVisible(true);
}
}
What's going wrong...
if (x < width) {
x = +10;
} else if (x >= 0) {
x = -10;
}
So, the above code is simply assigning either -10 or +10 to the x variable, it never increments/decrements the value. Use += and -= instead
if (x < width) {
x += 10;
} else if (x >= 0) {
x -= 10;
}
This will solve the immediate issue, but create a new one. It would be better to have a simple delta which was either positive or negative and is then simply applied to the variable
x += delta; // +/- speed
if (x + 20 >= width) {
x = width - 20;
delta *= -1
} ...
Additional fixes
Swing is not thread, you should not be using threads to change the state of the UI. Start by having a look at Concurrency in Swing and How to Use Swing Timers for more details.
You should also have a look at Painting in AWT and Swing and Performing Custom Painting as you should be preferring paintComponent over paint
The following is a rewrite of your example using a Swing Timer
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.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.ButtonGroup;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JToggleButton;
import javax.swing.Timer;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
JFrame frame = new JFrame();
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
protected class TestPane extends JPanel {
private AnimationPanel animationPane;
public TestPane() {
setLayout(new BorderLayout());
animationPane = new AnimationPanel();
add(animationPane);
JToggleButton pauseButton = new JToggleButton("Pause");
pauseButton.setSelected(true);
pauseButton.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
animationPane.setPaused(pauseButton.isSelected());
}
});
JButton fasterButton = new JButton("Faster");
fasterButton.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
animationPane.setSpeed(animationPane.getSpeed() * 2);
}
});
JButton slowerButton = new JButton("Slower");
slowerButton.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
animationPane.setSpeed(animationPane.getSpeed() / 2);
}
});
JToggleButton horizontalButton = new JToggleButton("Horizontal");
JToggleButton verticalButton = new JToggleButton("Vertical");
horizontalButton.setSelected(true);
ButtonGroup bg = new ButtonGroup();
bg.add(horizontalButton);
bg.add(verticalButton);
horizontalButton.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
animationPane.setLeftToRight(horizontalButton.isSelected());
}
});
verticalButton.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
animationPane.setLeftToRight(!verticalButton.isSelected());
}
});
JPanel actionPane = new JPanel();
actionPane.add(pauseButton);
actionPane.add(slowerButton);
actionPane.add(fasterButton);
actionPane.add(horizontalButton);
actionPane.add(verticalButton);
add(actionPane, BorderLayout.SOUTH);
}
}
public class AnimationPanel extends JPanel {
public int getSpeed() {
return speed;
}
public void setSpeed(int speed) {
this.speed = speed;
}
public boolean isLeftToRight() {
return isLeftToRight;
}
public void setLeftToRight(boolean isLeftToRight) {
this.isLeftToRight = isLeftToRight;
}
private boolean paused = true;
private int speed = 10;
private boolean isLeftToRight = true;
private Point origin = new Point(190, 190);
private Timer timer;
public AnimationPanel() {
timer = new Timer(16, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
movement();
repaint();
}
});
}
#Override
public Dimension getPreferredSize() {
return new Dimension(400, 400);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
g2d.setColor(Color.BLUE);
g2d.fillOval(origin.x, origin.y, 20, 20);
g2d.dispose();
}
public boolean isPaused() {
return paused;
}
public void setPaused(boolean paused) {
this.paused = paused;
if (paused) {
timer.stop();
} else {
timer.start();
}
}
public void movement() {
int height = this.getHeight();
int width = this.getWidth();
if (isLeftToRight) {
origin.x += speed;
if (origin.x + 20 >= width) {
speed *= -1;
origin.x = width - 20;
} else if (origin.x <= 0) {
speed *= -1;
origin.x = 0;
}
} else {
origin.y += speed;
if (origin.y + 20 >= height) {
speed *= -1;
origin.y = height - 20;
} else if (origin.y <= 0) {
speed *= -1;
origin.y = 0;
}
}
}
}
}
A "different" approach
I don't like delta based animations, I think they are shorted sighted and they generally produce bad results. Where possible, I prefer to make use of time based animations. That is, based on a given time, move the object over a given range.
When done right, this is really flexible. It allows the system to drop frames automatically without the animation "stalling" and generally produces nicer animation which is generally easier to manage and maintained.
This does, however, introduce more complexity, but if you spend the time to generalise the workflows, they can be re-used relatively easily.
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.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import java.time.Duration;
import java.time.Instant;
import javax.swing.ButtonGroup;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JToggleButton;
import javax.swing.Timer;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
JFrame frame = new JFrame();
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
protected class TestPane extends JPanel {
private AnimationPanel animationPane;
public TestPane() {
setLayout(new BorderLayout());
animationPane = new AnimationPanel();
add(animationPane);
JToggleButton pauseButton = new JToggleButton("Run");
pauseButton.setSelected(true);
pauseButton.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
if (pauseButton.isSelected()) {
pauseButton.setText("Run");
} else {
pauseButton.setText("Pause");
}
animationPane.setPaused(pauseButton.isSelected());
}
});
// JButton fasterButton = new JButton("Faster");
// fasterButton.addActionListener(new ActionListener() {
// #Override
// public void actionPerformed(ActionEvent e) {
// animationPane.setSpeed(animationPane.getSpeed() * 2);
// }
// });
// JButton slowerButton = new JButton("Slower");
// slowerButton.addActionListener(new ActionListener() {
// #Override
// public void actionPerformed(ActionEvent e) {
// animationPane.setSpeed(animationPane.getSpeed() / 2);
// }
// });
JToggleButton horizontalButton = new JToggleButton("Horizontal");
JToggleButton verticalButton = new JToggleButton("Vertical");
horizontalButton.setSelected(true);
ButtonGroup bg = new ButtonGroup();
bg.add(horizontalButton);
bg.add(verticalButton);
// horizontalButton.addActionListener(new ActionListener() {
// #Override
// public void actionPerformed(ActionEvent e) {
// animationPane.setLeftToRight(horizontalButton.isSelected());
// }
// });
//
// verticalButton.addActionListener(new ActionListener() {
// #Override
// public void actionPerformed(ActionEvent e) {
// animationPane.setLeftToRight(!verticalButton.isSelected());
// }
// });
JPanel actionPane = new JPanel();
actionPane.add(pauseButton);
// actionPane.add(slowerButton);
// actionPane.add(fasterButton);
// actionPane.add(horizontalButton);
// actionPane.add(verticalButton);
add(actionPane, BorderLayout.SOUTH);
}
}
public class AnimationPanel extends JPanel {
public enum Direction {
VERTICAL, HORIZONTAL
}
private Direction direction = Direction.HORIZONTAL;
private Point2D origin = new Point2D.Double(200, 200);
private Animator animator;
private Range<Double> range;
private Duration duration = Duration.ofSeconds(5);
private Ellipse2D dot = new Ellipse2D.Double(0, 0, 20, 20);
public AnimationPanel() {
animator = new Animator(new Animator.Observer() {
#Override
public void animatorDidTick(Animator animator, double progress) {
double nextValue = range.valueAt(progress);
if (direction == Direction.HORIZONTAL) {
origin.setLocation(nextValue, origin.getY());
}
repaint();
}
#Override
public void animatorDidComplete(Animator animator) {
double targetPoint = range.getTo();
if (direction == Direction.HORIZONTAL) {
range = getDotHorizontalRange();
if (targetPoint != range.getFrom()) {
range.reverse();
}
}
animator.setDuration(duration);
resume();
}
});
}
#Override
public Dimension getPreferredSize() {
return new Dimension(400, 400);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
g2d.setColor(Color.BLUE);
g2d.drawRect(0, 0, getWidth() - 1, getHeight() - 1);
g2d.draw(new Line2D.Double(0, getHeight() / 2, getWidth(), getHeight() / 2));
g2d.draw(new Line2D.Double(getWidth() / 2, 0, getWidth() / 2, getHeight()));
g2d.translate(origin.getX() - (dot.getWidth() / 2d), origin.getY() - (dot.getHeight() / 2d));
g2d.fill(dot);
g2d.dispose();
}
protected Range<Double> getDotHorizontalRange() {
return new DoubleRange(dot.getWidth() / 2, getWidth() - (dot.getWidth() / 2));
}
protected double getHorizontalRangeDistance() {
return ((DoubleRange)getDotHorizontalRange()).getDistance();
}
public void setPaused(boolean paused) {
if (paused) {
animator.pause();
} else {
if (range == null) {
initialiseRange();
}
animator.resume();
}
}
protected void resume() {
if (range == null) {
// Try and force a restart...
setPaused(false);
}
animator.resume();
}
protected void initialiseRange() {
if (direction == Direction.HORIZONTAL) {
double currentX = origin.getX();
// Assume a positive intial direction
double avaliableRange = Math.abs(getHorizontalRangeDistance());
double distance = avaliableRange - currentX;
int remainingTime = (int)(duration.toMillis() * (distance / avaliableRange));
animator.setDuration(Duration.ofMillis(remainingTime));
range = new DoubleRange((double)currentX, getDotHorizontalRange().getTo());
}
}
}
public abstract class Range<T> {
private T from;
private T to;
public Range(T from, T to) {
this.from = from;
this.to = to;
}
public T getFrom() {
return from;
}
public T getTo() {
return to;
}
#Override
public String toString() {
return "From " + getFrom() + " to " + getTo();
}
public abstract T valueAt(double progress);
public void reverse() {
T nextFrom = to;
to = from;
from = nextFrom;
}
}
public class DoubleRange extends Range<Double> {
public DoubleRange(Double from, Double to) {
super(from, to);
}
public Double getDistance() {
return getTo() - getFrom();
}
#Override
public Double valueAt(double progress) {
double distance = getDistance();
double value = distance * progress;
value += getFrom();
return value;
}
}
public class Animator {
public enum State {
STOP, PAUSE, RUN
}
public interface Observer {
public void animatorDidTick(Animator animator, double progress);
public void animatorDidComplete(Animator animator);
}
private Duration duration = Duration.ofSeconds(5);
// Used to manage pause support. This will be
// added onto the "live" runtime when the
// animator is running
private Duration previousRuntime = Duration.ZERO;
private Instant epoch;
private Observer observer;
private State state = State.STOP;
// This is actually used to manage the "ticks"
private Timer ticker = new Timer(5, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
if (epoch == null) {
epoch = Instant.now();
}
double progress = getProgressAtCurrentTime();
observer.animatorDidTick(Animator.this, Math.max(0, Math.min(1.0, progress)));
if (progress >= 1.0) {
progress = 1.0;
stop();
observer.animatorDidComplete(Animator.this);
}
}
});
public Animator(Observer observer) {
this.observer = observer;
}
public void setDuration(Duration duration) {
this.duration = duration;
}
public boolean isPaused() {
return state == State.PAUSE;
}
public boolean isRunning() {
return state == State.RUN;
}
public boolean isStopped() {
return state == State.STOP;
}
public void pause() {
ticker.stop();
if (epoch != null) {
Duration runtime = Duration.between(epoch, Instant.now());
previousRuntime = previousRuntime.plus(runtime);
state = State.PAUSE;
}
epoch = null;
}
public void resume() {
state = State.RUN;
ticker.start();
}
protected double getProgressAtCurrentTime() {
Duration runtime = Duration.ZERO;
if (epoch != null) {
// The delta time between when we started and now
runtime = Duration.between(epoch, Instant.now());
}
// Plus any additonal time which was recored
runtime = runtime.plus(previousRuntime);
return runtime.toMillis() / (double) duration.toMillis();
}
// This is for internal reset purposes
protected void stop() {
ticker.stop();
state = State.STOP;
previousRuntime = Duration.ZERO;
epoch = null;
}
}
}
The above example makes use of a concept of "normalised" time. That is, any given animation transitions over a time range of 0-1. This makes it incredibly easy to change the speed. Want to to go faster? Decrease the duration. Slower? Increase the duration. Everything else is done by simply calculating the required properties against a "from" and "to" state and the current "normalised time" value.
For example, look at the animation above. The dot starts at the halfway point, but the time it takes to get to the other side is no different then the time it takes to return ALL the way to far side (the speed doesn't change), this is because, the initial duration is calculated based on a delta of the whole range and the current position (that is, the initial duration is 50% of the desired duration).
The above example also allows you to "pause" the running animation and when it resumes, it will continue as if nothing happened.
Animation, I mean, really good animation, is complicated. Don't believe me, take a look at move dot between 2 points jframe (which I've spent the last few days building out as personal projects to run in Java and MacOS, because apparently I don't have a live) and How can I implement easing functions with a thread which takes a deep dive into just one aspect of the animation theory and don't even get me started on "time line" animation.
I'm new to Eclipse, recently swapped from Bluej which ran my codes reliably. In Eclipse, it sometimes runs and sometimes just doesn't run the paint method and I'm not sure why. The same code was running this morning and now it decides to not run and I'm not sure what to do.
Main method:
import java.awt.Container;
import java.awt.Dimension;
import javax.swing.JFrame;
import javax.swing.JPanel;
import Asset.Paddle;
import Asset.Puck;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.event.MouseEvent;
import java.awt.BorderLayout;
public class setup implements KeyListener, MouseListener, MouseMotionListener, Runnable {
int width = 100;
int height = 100;
int scale = 8;
public static setup setup;
JFrame frame;
JPanel main;
Graphic graphic;
Puck puck;
Paddle paddle1,paddle2;
boolean running, up = true, up2 = true;
boolean menu = false, b1, b2, b3;
int winSize;
public setup() {
puck = new Puck((width*scale)/2,(width*scale)/2,20,20);
paddle1 = new Paddle(width*scale/8-20,height*scale/2,20,100);
paddle2 = new Paddle(width*scale/8*7,height*scale/2,20,100);
frame();
}
public void frame() { //Frame setup
frame = new JFrame("Pong");
frame.setSize(width * scale,height * scale);
frame.setLayout(new BorderLayout());
frame.setResizable(false);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Container c1 = frame.getContentPane();
Dimension winSize = frame.getSize();
System.out.println(winSize);
graphic = new Graphic(puck,paddle1,paddle2);
graphic.addKeyListener(this);
graphic.addMouseListener(this);
graphic.addMouseMotionListener(this);
main = new JPanel();
main.setLayout(new BorderLayout());
main.setSize(width * scale,height * scale);
main.add(graphic,BorderLayout.CENTER);
start();
c1.add(main);
graphic.requestFocus();
}
public void start() { //running = true
new Thread(this).start();
running = true;
menu = true;
RENDER();
}
public void stop() {
running = false;
}
public void run() { //Game
while(running == true) {
try {
Thread.sleep(10);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
if(puck.getY() < 0 || puck.getY() > height * scale) {
puck.reverseY();
}
paddle1.run();
paddle2.run();
puck.run();
RENDER();
}
}
public void RENDER() {
graphic.UPDATEPADDLE(paddle1,paddle2);
graphic.UPDATEPUCK(puck);
graphic.repaint();
}
public void keyPressed(KeyEvent evt) {
if(evt.getKeyCode() == 81) { // Q
paddle1.setYVel(-2);
up = true;
}
if(evt.getKeyCode() == 65) { // A
paddle1.setYVel(2);
up = false;
}
if(evt.getKeyCode() == 80) { // P
paddle2.setYVel(-2);
up2 = true;
}
if(evt.getKeyCode() == 76) {
paddle2.setYVel(2);
up2 = false;
}
}
public void keyReleased(KeyEvent evt) {
if(evt.getKeyCode() == 81 && up ) { // Q
paddle1.setYVel(0);
}
if(evt.getKeyCode() == 65 && !up) { // A
paddle1.setYVel(0);
}
if(evt.getKeyCode() == 80 && up2) { // P
paddle2.setYVel(0);
}
if(evt.getKeyCode() == 76 && !up2) { // L
paddle2.setYVel(0);
}
}
public void keyTyped(KeyEvent evt) {}
public void mouseClicked(MouseEvent e) {
// if(e.getX() > 375 && e.getX() < 375 + 200 && e.getY() > 400 && e.getY() < 400 + 50) {
// menu = false;
// System.out.println("clicked");
// graphic.UPDATEBUTTON(b1,b2,b3);
// graphic.UPDATEMENU(menu);
// start();
// graphic.repaint();
// }
}
public void mouseEntered(MouseEvent e) {
}
public void mouseExited(MouseEvent e) {
}
public void mousePressed(MouseEvent e) {
}
public void mouseReleased(MouseEvent e) {
}
public static void main(String[] args) {
setup = new setup();
}
public void mouseDragged(MouseEvent e) {
}
public void mouseMoved(MouseEvent e) {
if(e.getX() > 375 && e.getX() < 375 + 200 && e.getY() > 400 && e.getY() < 400 + 50) {
b1 = true;
graphic.UPDATEBUTTON(b1,b2,b3);
graphic.repaint();
}
else {
b1 = false;
graphic.UPDATEBUTTON(b1,b2,b3);
graphic.repaint();
}
}
}
Paddle:
package Asset;
public class Paddle {
double x, y, yVel, h, w;
public Paddle(double xx, double yy, int width, int height) {
x = xx;
y = yy;
h = height;
w = width;
}
public int getX() {
return (int)x;
}
public int getY() {
return (int)y;
}
public int getH() {
return (int)h;
}
public int getW() {
return (int)w;
}
public void setYVel(int yVelocity) {
yVel = yVelocity;
}
public void run() {
y += yVel;
if(y < 0) {
yVel = 0;
y = 0;
}
}
}
Puck:
package Asset;
public class Puck {
double x,y,w,h;
double xVel = 0;
double yVel = 3;
public Puck(double xx, double yy,int width,int height) {
x = xx;
y = yy;
w = width;
h = height;
x++;
}
public void reverseY() {
yVel *= -1;
}
public void reverseX() {
xVel *= -1;
}
public int getX() {
return (int)x;
}
public int getY() {
return (int)y;
}
public int getW() {
return (int)w;
}
public int getH() {
return (int)h;
}
public void run() {
x += xVel;
y += yVel;
}
}
Setup:
import java.awt.*;
import javax.swing.*;
import Asset.Paddle;
import Asset.Puck;
public class Graphic extends JPanel {
private static final long serialVersionUID = 2273791975624707192L;
Puck ppuck;
Paddle ppaddle1,ppaddle2;
boolean mmenu = true;
boolean bb1,bb2,bb3;
public Graphic(Puck puck,Paddle paddle1,Paddle paddle2) {
ppuck = puck;
ppaddle1 = paddle1;
ppaddle2 = paddle2;
}
public void UPDATEMENU(boolean menu) {
mmenu = menu;
}
public void UPDATEPADDLE(Paddle paddle1, Paddle paddle2) {
ppaddle1 = paddle1;
ppaddle2 = paddle2;
}
public void UPDATEPUCK(Puck puck) {
ppuck = puck;
}
public void UPDATEBUTTON(boolean b1,boolean b2, boolean b3) {
bb1 = b1;
bb2 = b2;
bb3 = b3;
}
public void paint (Graphics g) {
super.paint(g);
g.setColor(Color.black);
g.fillRect(0, 0, 1000, 1000);
if (mmenu) { //menu
if(bb1) {
g.setColor(Color.blue);
g.setFont(new Font("TimesRoman", Font.PLAIN, 50));
g.drawString("START", 390, 440);
}
else {
g.setColor(Color.white);
g.setFont(new Font("TimesRoman", Font.PLAIN, 50));
g.drawString("START", 390, 440);
}
}
else {
g.setColor(Color.white);
g.fillOval(ppuck.getX(), ppuck.getY(), ppuck.getW(), ppuck.getH());
g.fillRect(ppaddle1.getX(), ppaddle1.getY(), ppaddle1.getW(), ppaddle1.getH());
g.fillRect(ppaddle2.getX(), ppaddle2.getY(), ppaddle2.getW(), ppaddle2.getH());
}
}
}
So, two "basic" problems...
One, if you modify the UI after the frame is visible, you must call revalidate and repaint too trigger a layout and paint pass. A simpler solution, in your case, would be to call setVisible AFTER you've established the UI
public void frame() { //Frame setup
frame = new JFrame("Pong");
frame.setSize(width * scale, height * scale);
frame.setLayout(new BorderLayout());
frame.setLocationRelativeTo(null);
//frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Container c1 = frame.getContentPane();
//...
start();
c1.add(main);
graphic.requestFocus();
frame.setVisible(true);
}
Second...
This one's a little more complicated, but, you start your Thread and then update the state which it relies on to keep running
public void start() { //running = true
new Thread(this).start();
running = true;
menu = true;
RENDER();
}
While very, very unlikely, it's possible that the thread will inspect the state before you change it ... or because of the way the memory model works, won't see the change.
Better to set it before hand...
public void start() { //running = true
running = true;
new Thread(this).start();
menu = true;
RENDER();
}
You should also consider making it volatile
Having said that...
You're going around it all the wrong way.
To start with, don't try and do all the rendering for all the states in the single view, instead, use seperate views to different states (such as the start screen and the game screen).
You could then make use of CardLayout or simply overlay the containers onto of each other when you want to switch between them.
Next, you should avoid using KeyListener, it's troublesome at the best of times. Instead, make us of the key bindings API, then you won't need to post another question about why KeyListener has stopped working.
Next, Swing is single threaded and not thread safe. This means you should not be performing blocking or long running operations within the context of the Event Dispatching Thread or updating the UI or a state the UI depends on from outside of it.
Take a look at Concurrency in Swing for more details.
The simple solution in this case is probably to use a Swing Timer, see How to use Swing Timers for more details.
I would, personally, make the "game" panel responsible for setting up the input and rendering management, but that's me.
I'm new to swing and I am trying to make the square move but only when the key is released (ex W) but when i hold down the key the square just moves
KeyListener Class
I want to make sure that a key was pressed and if it is still pressed it should return false but true if it was pressed then released
package javaGD.GameAssistant.Input;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
public class KeyManager implements KeyListener {
private boolean[] keys;
public KeyManager() {
keys = new boolean[256];
}
#Override
public void keyPressed(KeyEvent e) {
keys[e.getKeyCode()] = true;
}
#Override
public void keyReleased(KeyEvent e) {
keys[e.getKeyCode()] = false;
}
#Override
public void keyTyped(KeyEvent e) {
}
public boolean KeyPressed(int keycode) {
return keys[keycode];
}
public boolean KeyReleased(int keycode) {
return keys[keycode];
}
}
Class where the square should move.
KeyListener is inherited from GameAssistant(JFrame is created with the KeyListener)
package TestCode;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.event.KeyEvent;
import javaGD.GameAssistant.GameAssistant;
public class Game extends GameAssistant {
public int x, xSpeed, y, ySpeed;
public Game(String title, int width, int height, boolean makeResizable) {
super(title, width, height, makeResizable);
}
#Override
public void setup() {
x = 0;
y = 0;
xSpeed = 5;
ySpeed = 5;
}
#Override
public void update() {
if (this.Keyboard().KeyReleased(KeyEvent.VK_D)) {
x += xSpeed;
}
if (this.Keyboard().KeyReleased(KeyEvent.VK_A)) {
x -= xSpeed;
}
if (this.Keyboard().KeyReleased(KeyEvent.VK_W)) {
y -= ySpeed;
}
if (this.Keyboard().KeyReleased(KeyEvent.VK_S)) {
y += ySpeed;
}
}
#Override
public void draw(Graphics g) {
g.setColor(Color.BLACK);
g.fillRect(x, y, 20, 20);
}
}
KeyReleased should be returning !keys[keycode] Otherwise it will return false when released and true when pressed
public boolean KeyReleased(int keycode) {
return !keys[keycode];
}
I would also recommend using the Key bindings API over KeyListener as it's more reliable and re-usable.
If you are only planning on a limited number of input operations, I would use Set and a enum, this way you can decouple the "how" from the "what".
You update method doesn't care "how" the inputs are managed, only the "what is the state"
Conceptually, maybe something like...
public enum GameInput {
UP, DOWN, LEFT, RIGHT;
}
public class KeyManager implements KeyListener {
private Set<GameInput> inputs = new HashSet<>();
public KeyManager() {
}
#Override
public void keyPressed(KeyEvent e) {
// Check the key code, verify if it's one of the configured
// actions keys
// The key code could come from a configuration file which might
// be customisable by the user...
if (e.getKeyCode() == KeyEvent.VK_W) {
inputs.add(GameInput.UP);
} else if (e.getKeyCode() == KeyEvent.VK_S) {
// etc...
} // etc...
}
#Override
public void keyReleased(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_W) {
inputs.remove(GameInput.UP);
} else if (e.getKeyCode() == KeyEvent.VK_S) {
// etc...
} // etc...
}
#Override
public void keyTyped(KeyEvent e) {
}
public boolean isKeyPressed(GameInput input) {
return inputs.contains(input);
}
public boolean isKeyReleased(GameInput input) {
return !isKeyPressed(input);
}
}
And your update method might look like...
#Override
public void update() {
if (this.Keyboard().isKeyReleased(GameInput.RIGHT)) {
x += xSpeed;
}
if (this.Keyboard().isKeyReleased(GameInput.LEFT)) {
x -= xSpeed;
}
if (this.Keyboard().isKeyReleased(GameInput.UP)) {
y -= ySpeed;
}
if (this.Keyboard().isKeyReleased(GameInput.DOWN)) {
y += ySpeed;
}
}
Now your update method doesn't care "how" the inputs are generated, only what to do when they are (or aren't) set.
Personally, I'd use a InputManager class and further decouple it, so inputs could be generate via other means, like buttons, mouse inputs, game pads, etc...
Runnable example...
Conceptually, this should work. I say "conceptually", because while I was testing I came across a number of "weird" issues that I can't contribute to either Java (1.8) or MacOS - or because they are a combination of both...more details below...
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.util.HashSet;
import java.util.Set;
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 Test {
public static void main(String[] args) {
new Test();
}
public Test() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
JFrame frame = new JFrame();
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
private Box box = new Box();
public TestPane() {
InputMap im = getInputMap(WHEN_IN_FOCUSED_WINDOW);
ActionMap am = getActionMap();
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_W, 0, false), "Up.pressed");
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_W, 0, true), "Up.released");
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_S, 0, false), "Down.pressed");
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_S, 0, true), "Down.released");
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_A, 0, false), "Left.pressed");
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_A, 0, true), "Left.released");
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_D, 0, false), "Right.pressed");
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_D, 0, true), "Right.released");
am.put("Up.pressed", new KeyAction(InputAction.UP, true));
am.put("Up.released", new KeyAction(InputAction.UP, false));
am.put("Down.pressed", new KeyAction(InputAction.DOWN, true));
am.put("Down.released", new KeyAction(InputAction.DOWN, false));
am.put("Left.pressed", new KeyAction(InputAction.LEFT, true));
am.put("Left.released", new KeyAction(InputAction.LEFT, false));
am.put("Right.pressed", new KeyAction(InputAction.RIGHT, true));
am.put("Right.released", new KeyAction(InputAction.RIGHT, false));
Timer timer = new Timer(5, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
box.update(getBounds());
repaint();
}
});
timer.start();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(400, 400);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
box.draw(g);
}
}
public class Box {
public int x, xSpeed, y, ySpeed, width, height;
public Box() {
x = 0;
y = 0;
xSpeed = 1;
ySpeed = 1;
width = 10;
height = 10;
}
public void update(Rectangle bounds) {
if (!InputManager.INSTANCE.isSet(InputAction.LEFT)) {
x -= xSpeed;
}
if (!InputManager.INSTANCE.isSet(InputAction.RIGHT)) {
x += xSpeed;
}
if (InputManager.INSTANCE.isSet(InputAction.UP) && InputManager.INSTANCE.isSet(InputAction.DOWN)) {
//
} else if (!InputManager.INSTANCE.isSet(InputAction.UP)) {
y -= ySpeed;
} else if (!InputManager.INSTANCE.isSet(InputAction.DOWN)) {
y += ySpeed;
}
if (x < bounds.x) {
x = 0;
} else if (x + width > (bounds.x + bounds.width)) {
x = bounds.x + (bounds.width - width);
}
if (y < bounds.y) {
y = 0;
} else if (y + height > (bounds.y + bounds.height)) {
y = bounds.y + (bounds.height - height);
}
}
public void draw(Graphics g) {
g.setColor(Color.BLACK);
g.fillRect(x, y, width, height);
}
}
public enum InputAction {
UP, DOWN, LEFT, RIGHT;
}
public enum InputManager {
INSTANCE;
private Set<InputAction> actions = new HashSet<>();
public void set(InputAction action) {
actions.add(action);
}
public void remove(InputAction action) {
actions.remove(action);
}
public boolean isSet(InputAction action) {
return actions.contains(action);
}
}
public class KeyAction extends AbstractAction {
private InputAction action;
private boolean apply;
public KeyAction(InputAction action, boolean apply) {
this.action = action;
this.apply = apply;
}
#Override
public void actionPerformed(ActionEvent e) {
System.out.println("!");
if (apply) {
System.out.println("Apply " + action);
InputManager.INSTANCE.set(action);
} else {
System.out.println("Remove " + action);
InputManager.INSTANCE.remove(action);
}
}
}
}
TL;DR
While testing the above example, I came across a bizarre number of issues, I've not seen before (not that I've been doing this kind of thing recently).
When using KeyListener, if I pressed (and held) two buttons, I could see the "pressed" action, but there was no repeating events, which is something I would normally expect (for any keys). When I released on key, I saw the "release" action, but when I pressed (and held it), no new "press" action was generated.
I tried the key bindings API (as demonstrated above) and still had no success (similar results).
I then attached a AWTEventListener directly to the event queue and monitored ALL the key strokes.
I noted that, some times (even is just tapping a key repeatedly) that "pressed" might not be generated.
I also noted that holding one or more keys down, releasing and pressing a key again, more often then not, did not generate a new press event (only release events)
I'm using macOS 10.13.6 and Java 1.8.0_144-b01 - it could be bug in either or both, but I don't have the means to test it otherwise
Update...
So, after updating from Java 1.8 to Java 1.10, the above mentioned issue seems to be resoled - however, this highlights another, hardware issue, where only a certain number of keys can be actively pressed at a time - See How do I remove the limit on PC keyboard button presses? for more details
I am just trying to program a little football game and after doing all the painting and initializing the players and the ball I am now at the point where I try to move the player. I watched some tutorial how to program movement in games but they all using KeyListeners. As I wrote online KeyListener is not a good approach. (Is there still any case to use KeyListeners?). However, I am trying to implement a smooth and for OO manners correct movement. I couldnt find online resources which teach that in the "correct" way. I specify the "correct" way because of suggestions I have read from top Java programmer on SO. I am struggling with WHAT to use for movement and WHERE to implement it? I thought about using Actions and implement it in an inner class of Player maybe? Is this the right approach?
Here is my code until now:
Speedball:
package SpeedballMinimal;
import javax.swing.*;
import java.awt.*;
public class Speedball{
public static final int AREA_WIDTH = 1400;
public static final int AREA_HEIGHT = 700;
public Speedball() {
}
private void start() {
JFrame mainFrame = new JFrame("Speedball");
SpeedballPanel panel = new SpeedballPanel();
mainFrame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
mainFrame.setLayout(new BorderLayout());
mainFrame.setSize(AREA_WIDTH, AREA_HEIGHT);
mainFrame.add(panel, BorderLayout.CENTER);
mainFrame.setLocationRelativeTo(null);
mainFrame.setResizable(false);
mainFrame.setVisible(true);
}
public static void main(String[] args) {
Speedball speedball = new Speedball();
speedball.start();
}
}
SpeedballPanel:
package SpeedballMinimal;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class SpeedballPanel extends JPanel implements ActionListener {
private Timer timer;
private Renderer renderer;
private Player player1;
private Player player2;
public SpeedballPanel() {
initPanel();
initUserInteractions();
initTimer();
this.renderer = new Renderer();
this.player1 = new Player(300, 300);
this.player2 = new Player(500, 500);
}
private void initPanel() {
this.setSize(Speedball.AREA_WIDTH, Speedball.AREA_HEIGHT);
}
#Override
public void paintComponent(Graphics g) {
renderer.drawBackground(g, getWidth(), getHeight());
renderer.drawPlayer(g, player1.getX(), player1.getY(), player1.getWidth(), player1.getHeight());
renderer.drawPlayer(g, player2.getX(), player2.getY(), player2.getWidth(), player2.getHeight());
}
/*
* Playermovement
*/
private void playerMoveUp(Player player) {
player.setY(player.getY() - 10);
System.out.println("Player Y: " + player.getY());
}
private void playerMoveDown(Player player) {
player.setY(player.getY() + 10);
System.out.println("Player Y: " + player.getY());
}
private void playerMoveLeft(Player player) {
player.setX(player.getX() - 10);
System.out.println("Player X: " + player.getX());
}
private void playerMoveRigth(Player player) {
player.setX(player.getX() + 10);
System.out.println("Player X: " + player.getX());
}
/*
* Actions for playermovement
*/
Action player1MoveUp = new AbstractAction() {
#Override
public void actionPerformed(ActionEvent e) {
playerMoveUp(player1);
}
};
Action player1MoveDown = new AbstractAction() {
#Override
public void actionPerformed(ActionEvent e) {
playerMoveDown(player1);
}
};
Action player1MoveLeft = new AbstractAction() {
#Override
public void actionPerformed(ActionEvent e) {
playerMoveLeft(player1);
}
};
Action player1MoveRight = new AbstractAction() {
#Override
public void actionPerformed(ActionEvent e) {
playerMoveRigth(player1);
}
};
Action player2MoveUp = new AbstractAction() {
#Override
public void actionPerformed(ActionEvent e) {
playerMoveUp(player2);
}
};
Action player2MoveDown = new AbstractAction() {
#Override
public void actionPerformed(ActionEvent e) {
playerMoveDown(player2);
}
};
Action player2MoveLeft = new AbstractAction() {
#Override
public void actionPerformed(ActionEvent e) {
playerMoveLeft(player2);
}
};
Action player2MoveRight = new AbstractAction() {
#Override
public void actionPerformed(ActionEvent e) {
playerMoveRigth(player2);
}
};
/*
* Define keys for user interaction
*/
private void initUserInteractions() {
//define InputMaps
this.getInputMap().put(KeyStroke.getKeyStroke("W"), "player1MoveUp");
this.getInputMap().put(KeyStroke.getKeyStroke("S"), "player1MoveDown");
this.getInputMap().put(KeyStroke.getKeyStroke("A"), "player1MoveLeft");
this.getInputMap().put(KeyStroke.getKeyStroke("D"), "player1MoveRight");
this.getInputMap().put(KeyStroke.getKeyStroke("UP"), "player2MoveUp");
this.getInputMap().put(KeyStroke.getKeyStroke("DOWN"), "player2MoveDown");
this.getInputMap().put(KeyStroke.getKeyStroke("LEFT"), "player2MoveLeft");
this.getInputMap().put(KeyStroke.getKeyStroke("RIGHT"), "player2MoveRight");
//define ActionMaps
this.getActionMap().put("player1MoveUp", player1MoveUp);
this.getActionMap().put("player1MoveDown", player1MoveDown);
this.getActionMap().put("player1MoveLeft", player1MoveLeft);
this.getActionMap().put("player1MoveRight", player1MoveRight);
this.getActionMap().put("player2MoveUp", player2MoveUp);
this.getActionMap().put("player2MoveDown", player2MoveDown);
this.getActionMap().put("player2MoveLeft", player2MoveLeft);
this.getActionMap().put("player2MoveRight", player2MoveRight);
}
private void initTimer() {
timer = new Timer(1, this);
timer.start();
}
#Override
public void actionPerformed(ActionEvent e) {
repaint();
}
}
Renderer:
package SpeedballMinimal;
import java.awt.*;
public class Renderer {
public Renderer() {
}
public void drawBackground(Graphics g, int areaWidth, int areaHeight) {
g.setColor(new Color(106, 237, 49));
g.fillRect(0,0, areaWidth, areaHeight);
}
public void drawPlayer(Graphics g, int playerX, int playerY, int playerWidth, int playerHeight) {
g.setColor(Color.BLACK);
g.fillOval(playerX, playerY, playerWidth, playerHeight);
}
}
Player:
package SpeedballMinimal;
import java.util.Random;
public class Player {
private int x;
private int y;
private int width;
private int height;
private int velocity = 2;
private Random rand = new Random();
public Player() {
this.x = 300;
this.y = 300;
this.width = 50;
this.height = 50;
}
public int getX() {
return x;
}
public void setX(int x) {
this.x = x;
}
public int getY() {
return y;
}
public void setY(int x) {
this.y = y;
}
public int getWidth() {
return width;
}
public void setWidth(int width) {
this.width = width;
}
public int getHeight() {
return height;
}
public void setHeight(int height) {
this.height = height;
}
}
EDIT:
I edited my Code to a minimal example. This code produces an oval on my JPanel. I tried to create movement for that oval with KeyBindings as suggested. I used this resources:
How to use Key Bindings instead of Key Listeners
https://docs.oracle.com/javase/tutorial/uiswing/misc/keybinding.html
I now want to fix the following problem:
I cant move both player at the same time and I couldnt find anything online how to fix this. When player1 is moving and I press the up key for player2, player1 stop moving. Now I want to know, how I can let both player move at the same time
EDIT 2:
Here is my new Code based on the answer of c0der
SpeedballPanel (class got changed):
package SpeedballMinimal;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
public class SpeedballPanel extends JPanel {
private Renderer renderer;
private Player player1;
private Player player2;
public SpeedballPanel() {
initPanel();
initUserInteractions();
this.renderer = new Renderer();
this.player1 = new Player(Team.ONE);
this.player2 = new Player(Team.TWO);
}
private void initPanel() {
this.setSize(Speedball.AREA_WIDTH, Speedball.AREA_HEIGHT);
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
renderer.drawBackground(g, getWidth(), getHeight());
renderer.drawField(g, getWidth(), getHeight());
renderer.drawGoals(g, getWidth(), getHeight());
renderer.drawPlayer(g, player1.getX(), player1.getY(), player1.getWidth(), player1.getHeight());
renderer.drawPlayer(g, player2.getX(), player2.getY(), player2.getWidth(), player2.getHeight());
}
/*
* Playermovement
*/
private void movePlayer(Player player, MovementDirection direction) {
if (direction == MovementDirection.UP) {
player.setY(player.getY() - 10);
}
if (direction == MovementDirection.DOWN) {
player.setY(player.getY() + 10);
}
if (direction == MovementDirection.LEFT) {
player.setX(player.getX() - 10);
}
if (direction == MovementDirection.RIGHT) {
player.setX(player.getX() + 10);
}
repaint();
}
/*
* Actions for playermovement
*/
Action player1MoveUp = new AbstractAction() {
#Override
public void actionPerformed(ActionEvent e) {
movePlayer(player1, MovementDirection.UP);
}
};
Action player1MoveDown = new AbstractAction() {
#Override
public void actionPerformed(ActionEvent e) {
movePlayer(player1, MovementDirection.DOWN);
}
};
Action player1MoveLeft = new AbstractAction() {
#Override
public void actionPerformed(ActionEvent e) {
movePlayer(player1, MovementDirection.LEFT);
}
};
Action player1MoveRight = new AbstractAction() {
#Override
public void actionPerformed(ActionEvent e) {
movePlayer(player1, MovementDirection.RIGHT);
}
};
Action player2MoveUp = new AbstractAction() {
#Override
public void actionPerformed(ActionEvent e) {
movePlayer(player2, MovementDirection.UP);
}
};
Action player2MoveDown = new AbstractAction() {
#Override
public void actionPerformed(ActionEvent e) {
movePlayer(player2, MovementDirection.DOWN);
}
};
Action player2MoveLeft = new AbstractAction() {
#Override
public void actionPerformed(ActionEvent e) {
movePlayer(player2, MovementDirection.LEFT);
}
};
Action player2MoveRight = new AbstractAction() {
#Override
public void actionPerformed(ActionEvent e) {
movePlayer(player2, MovementDirection.RIGHT);
}
};
/*
* Define keys for user interaction
*/
private void initUserInteractions() {
//define InputMaps
this.getInputMap().put(KeyStroke.getKeyStroke("W"), "player1MoveUp");
this.getInputMap().put(KeyStroke.getKeyStroke("S"), "player1MoveDown");
this.getInputMap().put(KeyStroke.getKeyStroke("A"), "player1MoveLeft");
this.getInputMap().put(KeyStroke.getKeyStroke("D"), "player1MoveRight");
this.getInputMap().put(KeyStroke.getKeyStroke("UP"), "player2MoveUp");
this.getInputMap().put(KeyStroke.getKeyStroke("DOWN"), "player2MoveDown");
this.getInputMap().put(KeyStroke.getKeyStroke("LEFT"), "player2MoveLeft");
this.getInputMap().put(KeyStroke.getKeyStroke("RIGHT"), "player2MoveRight");
//define ActionMaps
this.getActionMap().put("player1MoveUp", player1MoveUp);
this.getActionMap().put("player1MoveDown", player1MoveDown);
this.getActionMap().put("player1MoveLeft", player1MoveLeft);
this.getActionMap().put("player1MoveRight", player1MoveRight);
this.getActionMap().put("player2MoveUp", player2MoveUp);
this.getActionMap().put("player2MoveDown", player2MoveDown);
this.getActionMap().put("player2MoveLeft", player2MoveLeft);
this.getActionMap().put("player2MoveRight", player2MoveRight);
}
}
MovementDirection (enum got created):
package SpeedballMinimal;
public enum MovementDirection {
UP, DOWN, LEFT, RIGHT
}
Note: Remaining classes got not changed
Actually I dont want to move player2 random or automatically, I want it to move by arrow keys as seen in my definition of the InputMaps. But I still have the same problem, when I try to move player1 with WASD keys and try to move player2 at the same time with UP, DOWN, LEFT, RIGHT - arrow keys, that the first moving player stops to move and the other player starts to move, but they are not able to move at the same time. Sorry if I was unclear in my first post about the movement of the player.
From the code one can assume that you want the other ball be moved by the timer.
Apply changes as per the following code to move player 1 (key actions are moving palyer 2, although you wanted it to move player 1. I did not check why).
private void initTimer() {
timer = new Timer(500, this);
timer.start();
}
#Override
public void actionPerformed(ActionEvent e) {
//move player 1
randomMovePlayer(player1);
repaint();
}
//randomly move player
private void randomMovePlayer(Player player) {
Random rand = new Random(); //refactor it to a field
float direction = rand.nextFloat();
if(direction < .25) {
playerMoveLeft(player);
}else if(direction < .5) {
playerMoveUp(player);
}else if(direction < .75) {
playerMoveDown(player);
}else {
playerMoveRigth(player1);
}
//todo improve logic to get the right movement within bounds
}
Also at each of the four move methods you need to repaint after the move :
private void playerMoveUp(Player player) {
player.setY(player.getY() - 10);
System.out.println("Player Y: " + player.getY());
repaint(); //repaint after each move
}
Side note: all four moves could be handled by one method:
enum Direction{LEFT, RIGHT, UP, DOWN};
private void movePalyer(Player player, Direction dir) {
//todo move logic
}
In response to Edit 2:
Try to start a continuous movement when key is pressed (by starting a timer), and stop it when the same key is released.
//do on w key press
this.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_W,0, false), "player1MoveUp");
//do on w key release
this.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_W,0, true), "stop");
Action player1MoveUp = new AbstractAction() {
#Override
public void actionPerformed(ActionEvent e) {
move(new AbstractAction() {
#Override
public void actionPerformed(ActionEvent arg0) {
movePlayer(player1, MovementDirection.UP);
}
});
}
};
//note that you can't use the same timer for both
//players if you don't want a button release stop both
private void move(Action play) {
if((timer !=null) && timer.isRunning()) { //timer is a field
return;
}
timer = new Timer(100, play);
timer.setInitialDelay(0);
timer.start();
}
Action stop = new AbstractAction() {
#Override
public void actionPerformed(ActionEvent e) {
timer.stop();
}
};
I've created my own extended JPanel to able the user to sign on it, after that save the signature and delete from the panel:
public class PanelParaFirmar extends JPanel
{
private MouseHandler mouseHandler = new MouseHandler();
private int index = 0;
private Point[] arr = new Point[100000];
public PanelParaFirmar()
{
this.setBackground(Color.WHITE);
this.addMouseListener(mouseHandler);
this.addMouseMotionListener(mouseHandler);
}
protected void paintComponent(Graphics g)
{
this.paintComponents(g);
for (int i = 0; i < index - 1; i++)
g.drawLine(arr[i].x, arr[i].y, arr[i + 1].x, arr[i + 1].y);
}
private class MouseHandler extends MouseAdapter {
#Override
public void mousePressed(MouseEvent e) {
arr[index] = new Point(e.getX(), e.getY());
index++;
repaint();
}
#Override
public void mouseReleased(MouseEvent e) {
arr = new Point[100000];
index = 0;
}
#Override
public void mouseDragged(MouseEvent e) {
//updateUI();
//save();
arr[index] = new Point(e.getX(), e.getY());
index++;
repaint();
}
public void mouseExited(MouseEvent e) {}
public void mouseClicked(MouseEvent e) {}
public void mouseEntered(MouseEvent e) {}
public void mouseMoved(MouseEvent e) {}
}
};
Before click on it all it's right:
But when I started to sign, it repaints the rootPane:
How can I paint just the line?
In addition to the fact that you should override paintComponent and not paintComponents , and call its super implementation, here is how you could manage multiple lines for one signature .
Create a list of lines ( a line is nothing else than a list of Point) to represent the signature.
When the mouse is pressed, add a new line to the list, and add the current point to this line.
When the mouse is dragged, add the current point to the current line.
Finally, your paint method will paint each line one after the other, without making junctions between them :
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Point;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class PanelParaFirmar extends JPanel {
private final MouseHandler mouseHandler = new MouseHandler();
private final List<List<Point>> lines = new ArrayList<>();
private List<Point> currentLine;
public static void main(final String[] args) {
JFrame fr = new JFrame();
fr.setSize(400, 200);
fr.getContentPane().add(new PanelParaFirmar());
fr.setVisible(true);
}
public PanelParaFirmar() {
this.setBackground(Color.WHITE);
this.addMouseListener(mouseHandler);
this.addMouseMotionListener(mouseHandler);
}
protected void paintComponent(final Graphics g) {
super.paintComponent(g);
for (List<Point> line : lines) {
for (int i = 0; i < line.size() - 1; i++) {
Point thisPoint = line.get(i);
Point nextPoint = line.get(i + 1);
g.drawLine(thisPoint.x, thisPoint.y, nextPoint.x, nextPoint.y);
}
}
}
private class MouseHandler extends MouseAdapter {
#Override
public void mousePressed(final MouseEvent e) {
currentLine = new ArrayList<Point>();
currentLine.add(new Point(e.getX(), e.getY()));
lines.add(currentLine);
repaint();
}
#Override
public void mouseDragged(final MouseEvent e) {
Point p = new Point(e.getX(), e.getY());
currentLine.add(p);
repaint();
}
}
}
Also note that since you are using a MouseAdapter , you don't have to implement the methods you don't need (like mouseClicked).