Reputation: 126
I'm trying to make a barrel inside my game float downwards towards the sceen by itself, I've tried a few things and looked up some tutorials, but I can't seem to make it work. Maybe I am approaching this in the wrong way by trying to do this inside a paintcomponent()? Inside my JPanel I also have a car object that is an extension of a JPanel controlled by KeyListeners.
// this is inside a JPanel class that I made that's painting out the game
OilBarrel barrel = new OilBarrel();
@Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
road.paintComponent(g);
this.add(car);
this.add(barrel);
barrel.setBounds(barrelX, barrel.getBarrelY(), 150, 180);
int carX = car.getCarX();
car.setBounds(carX, carY, 800, 400);
barrel.setBounds(550, barrel.getBarrelY(), 800, 400);
while (true) {
barrel.move();
repaint();
try {
Thread.sleep(1);
} catch (Exception e) {
}
}
}
// this is the oilbarrel class
import java.awt.Dimension;
import java.awt.Image;
import javax.swing.ImageIcon;
import javax.swing.JLabel;
public class OilBarrel extends JLabel{
int barrelX;
int barrelY = 0;
public OilBarrel() {
setVisible(true);
this.setFocusable(true);
Image img = new ImageIcon(this.getClass().getResource("/oil.png")).getImage();
this.setIcon(new ImageIcon(img));
}
public void move(){
if(barrelY<1000)
barrelY +=1;
}
public int getBarrelY() {
return barrelY;
}
}
Upvotes: 1
Views: 93
Reputation: 347334
Swing is single threaded, this means that you for-loop
in your paintComponent
method is blocking the Event Dispatching Thread, preventing it from updating the UI.
See Concurrency in Swing for more details
The simplest solution to solving the problem would be to use a Swing Timer
, as it allows for a small delay to be scheduled off the EDT, but which triggers it's updates on the EDT, making it safe to update the UI from within.
Component animation isn't hard, but it does require a different work flow, 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 javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
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 JLabel label;
private int xDelta = 1;
public TestPane() {
setLayout(null);
label = new JLabel("*");
label.setSize(label.getPreferredSize());
label.setLocation(0, (200 - label.getHeight()) / 2);
add(label);
Timer timer = new Timer(10, new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
int x = label.getX();
x += xDelta;
if (x + label.getWidth() > getWidth()) {
x = getWidth() - label.getWidth();
xDelta *= -1;
} else if (x < 0) {
x = 0;
xDelta *= -1;
}
label.setLocation(x, label.getY());
}
});
timer.start();
}
@Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
}
}
As a personal preference, I would avoid using components for this job and lean towards a complete custom painting route.
There are lots of reasons for this, components are complex objects which require a lot of attention to detail. It's also difficult to apply additional effects or transformations to them
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
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 Test {
public static void main(String[] args) {
new Test();
}
public Test() {
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 Rectangle box;
private int xDelta = 1;
public TestPane() {
setLayout(null);
box = new Rectangle(0, 75, 50, 50);
Timer timer = new Timer(10, new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
int x = box.x;
x += xDelta;
if (x + box.width > getWidth()) {
x = getWidth() - box.width;
xDelta *= -1;
} else if (x < 0) {
x = 0;
xDelta *= -1;
}
box.setLocation(x, box.y);
repaint();
}
});
timer.start();
}
@Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
g2d.draw(box);
g2d.dispose();
}
}
}
Upvotes: 3
Reputation: 423
Use SwingUtilities.invokeLater when a thread needs to update the GUI. https://docs.oracle.com/javase/7/docs/api/javax/swing/SwingUtilities.html#invokeLater(java.lang.Runnable)
Thread t = new Thread(){
public void run(){
while( true ){
// queue on Event Dispatch Thread
SwingUtilities.invokeLater( new Runnable(){
public void run(){
barrel.move();
repaint();
}
});
try{
Thread.sleep( 100 );
}catch( InterruptedException ie ){}
}
}
};
t.start();
Also, lookup Swing Timer. I think that may be better.
Upvotes: 0