fenec
fenec

Reputation: 5806

multithreading with java swing for a simple 2d animation

my final goal for this application is to animate several items in the same JPanel at a different speed using a thread for each item.the first part is done however the items move at the same speed and i have no idea on how to fix this problem.

package javagamestutos;



import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Toolkit;
import java.util.ArrayList;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JPanel;


public class Board extends JPanel implements Runnable {


    private Star star;
    private Thread animator;
    ArrayList<Star> items=new ArrayList<Star>();


    public Board() {
        setBackground(Color.BLACK);
        setDoubleBuffered(true);
        star=new Star(25,0,0);
        Star star2=new Star(50,20,25);
        items.add(star2);
        items.add(star);
    }

    public void addNotify() {
        super.addNotify();
        animator = new Thread(this);
        animator.start();
    }

    public void paint(Graphics g) {
        super.paint(g);

        Graphics2D g2d = (Graphics2D)g;



        for (Star s : this.items) {
            g2d.drawImage(s.starImage, s.x, s.y, this);
        }


        Toolkit.getDefaultToolkit().sync();
        g.dispose();
    }

    public void run() {

        while(true){
            try {
                for (Star s : this.items) {
                    s.move();
                }
                repaint();

                Thread.sleep(star.delay);
            } catch (InterruptedException ex) {
                Logger.getLogger(Board.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
    }


}

here is the star class wich is the moving item.

package javagamestutos;

import java.awt.Image;
import javax.swing.ImageIcon;

/**
 *
 * @author fenec
 */
public class Star {
    Image starImage;
    int x,y;
    int destinationX=200,destinationY=226;
    boolean lockY=true;
    int delay;


    public Star(int delay,int initialX,int initialY){
        ImageIcon ii = new ImageIcon(this.getClass().getResource("star.png"));
        starImage = ii.getImage();
        x=initialX;
        y=initialY;
        this.delay=delay;
    }


    void moveToX(int destX){
        this.x += 1;
    }


    boolean validDestinatonX(){
        if(this.x==this.destinationX){
                this.lockY=false;
                return true;
            }
        else
            return false;
    }

    void moveToY(int destY){
        this.y += 1;
    }


    boolean validDestinatonY(){
        if(this.y==this.destinationY)
            return true;
        else
            return false;
    }

    void move(){

        if(!this.validDestinatonX() )
            x+=1;
        if(!this.validDestinatonY() && !this.lockY)
            y+=1;

        /*if(!this.validDestinatonY())
            y+=1;
        */
    }


}

and here is the skeleton of the animation that extends a JFrame :

package javagamestutos;
import javax.swing.JFrame;

public class Skeleton extends JFrame {

public Skeleton() {
        add(new Board());
        setTitle("Stars");
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        setSize(300, 280);
        setLocationRelativeTo(null);
        setVisible(true);
        setResizable(false);
    }
    public static void main(String[] args) {
        new Skeleton();
    }
}

do you have any idea how to achieve my goals?am i using threads proprely? thank you in advance.

Upvotes: 1

Views: 5344

Answers (5)

OscarRyz
OscarRyz

Reputation: 199324

That's because you're invoking the "move" method at a fixed rate specified by the delay of the first "start"

  Thread.sleep(star.delay);

So if you move them a little every "n" milliseconds, they will seems to move at the same peace.

If you want them to move at different speed, you have to move them in different thread ( you are using only one now ) Bear in mind the comment by omry,

EDIT

I did something similar just recently

I have two different things so animate so I have two timers ( timers use threads underneath, but they can repeat the execution code every fixed rate ).

The first apply the text to a JLabel every second ( 1000 ms )

    final Timer timer = new Timer();
    timer.scheduleAtFixedRate( new TimerTask() {
        public void run(){      
            setText();
        }
    }, 0, 1000 );

And other change the displaying image every 10 seconds ( 10,000 ms )

    final Timer imageTimer = new Timer();
    imageTimer.scheduleAtFixedRate( new TimerTask() {
        public void run() {
            setImage();
        }
    }, 0, 10000 );

I have a video of the result here:

enter image description here

For more advanced ( and nice ) time management you MUST take a look at the "Timing Framework" project which adds additional capabilities to timers.

Upvotes: 2

jfpoilpret
jfpoilpret

Reputation: 10519

I would suggest you take a look at the open source library trident which does just that, its author, Kirill Grouchnikov is well-known in the Swing world (he is the author of the famous Substance look & feel).

Trident should help you solve the problem of having different objects move at different speeds, without having to create one thread per object (which is a problem in the end).

Upvotes: 1

Tom Hawtin - tackline
Tom Hawtin - tackline

Reputation: 147164

Generally Swing components should be used from the AWT Event Dispatch Thread (EDT). repaint is one of the methods that is supposedly okay to use off EDT. However, your Star is not and should not be thread-safe.

The easiest approach is to go for EDT-only (at least to start with). Instead of using Thread use javax.swing.Timer which fires on the EDT.

Misc comments: There should be no need for your paint method to dispose of the graphics object sent to it, or for it to sync using Toolkit. The component need not be set to double-buffered, but should be set opaque (JPanel is not guaranteed to be opaque). You should just extend JComponent instead of JPanel, as this is not a panel. It's generally not a great idea for outer classes to implement Runnable. Prefer private variables.

Upvotes: 1

Omry Yadan
Omry Yadan

Reputation: 33686

if you are sure you want to paint in the threads, you can use :

update(getGraphics());

instead of repaint. this is generally considered bad practice, as you normally paint stuff in the AWT thread.

Upvotes: -1

akf
akf

Reputation: 39495

You should be painting in the AWTDispatchThread. To do that you will want to use something like SwingUtilities.invokeLater(Runnable); This applies not only to your animation, but to the creation and setting visible of your JFrame as well. Failing to do this could result in deadlocks with the painting thread. Also, when moving your painting operations into the SwingUtilites methods, you will not want to include any while(true) loops, as that will commandeer your painting thread.

Upvotes: 1

Related Questions