Marko Marinkovic
Marko Marinkovic

Reputation: 126

Making JLabel move across JPanel

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

Answers (2)

MadProgrammer
MadProgrammer

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

Tim OMalley
Tim OMalley

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

Related Questions