Ok, so the program's purpose is to just draw and oval and move it across the screen. The code compiles on Eclipse without an error, but when run, no oval is drawn or moved across the screen. I have been researching, and it seems that threads have to do a lot with this, but do I need one for this simple program? I am obviously new to GUI programming with Swing so I would appreciate an explanation or link to one for any additions to the program regarding threads or such related concepts.
public class Game extends JPanel
{
int x =0;
int y =0;
private void moveBall()
{
x+=1;
y+=1;
}
public void paint (Graphics g)
{
super.paint(g);
g.fillOval(x, y, 30, 30);
}
public static void main(String[] args) {
JFrame frame = new JFrame("Animation");
Game game = new Game();
frame.add(game);
frame.setVisible(true);
frame.setSize(300,400);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setResizable(false);
while (true)
{
game.moveBall();
game.repaint();
}
}
}
The likely problem is, that thread is running too fast for the UI, the UI is been shown well after the "ball" has left the visible area.
You need to do a couple of things...
First, you need to make sure that the updates are scheduled properly within the Event Dispatching Thread and secondly, that there is a short delay between updates. For example, 25fps is about a 40 millisecond delay between updates, 60fps is about 16 milliseconds
There are a number of ways to achieve this, depending what it is you hope to achieve, for example, you could simply use Thread.sleep
to cause the thread to pause for a small amount of time between updates. The problem with this is Swing is not thread safe and all updates to the UI should be made within the context of the Event Dispatching Thread.
While you program is only simply, it's possible that a paint cycle could run while you updating it's state, resulting in a dirty update.
Another solution might be to use a Swing Timer
which will allow you schedule updates at a regular interval which are triggered within the context of the Event Dispatching Thread, making it safer to use.
Have a look at Concurrency in Swing and How to use Swing Timers for more details.
As an 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 javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class BallAnimation {
/**
* @param args the command line arguments
*/
public static void main(String[] args) {
new BallAnimation();
}
public BallAnimation() {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
private int x = 0;
private int y = 0;
public TestPane() {
Timer timer = new Timer(40, new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
moveBall();
repaint();
}
});
timer.start();
}
protected void moveBall() {
x++;
y++;
}
@Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
g2d.setColor(Color.RED);
g2d.fillOval(x, y, 30, 30);
g2d.dispose();
}
}
}
As a side note, unless you really have reason to do so, you should avoid overriding paint
and instead use paintComponent
try your loop with sleep as simplest way to fit your code. main is actually a thread. and JFrame
creates its own thread.
while (true)
{
game.moveBall();
game.repaint();
try { Thread.sleep(50); } catch (Exception e){}
}
and I just realized, you dont paint your whole screen with a default color.
change your paint method to this
public void paint (Graphics g)
{
super.paint(g);
g.setColor(Color.white); //default color
g.fillRect(0, 0, getWidth(), getHeight()); // fill whole canvas
g.setColor(Color.black); //change color
g.fillOval(x, y, 30, 30); // draw oval
}