Alireza Farahani
Alireza Farahani

Reputation: 2549

Execute a part of code in specified time

I want to make an animation using java swing. I have a for loop that changes the location of an image slightly. My question is How can I make the each loop execution takes the specified amount of time?. Its what I've done but I don't know how to use wait() and don't know is there a better way.

// inside of a MyJPanel class extending JPanel
for (int i = 0; i < FJframe.FRAMES; i++){
    long start = System.currentTimeMillis();
    animationFrame = i;
    this.repaint();
    long end = System.currentTimeMillis();
    long remainder = (end - start);
    System.out.println(remainder);
    try {
        this.wait(200 - remainder);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

Edit

here my overrided Jpanel PaintComponent(): //some drawing drawChanges(g, getDelta(), animationFrame, FJPanle.FRAMES);

And inside drawChanges(Graphics g, ArrayList deltas, int frame, int frames):

// a switch_case with cases similar to this one.
case AGENT_ATTACK:
// tu phase badi animation e kill
    //drawImage(g2d, map[delta.getCell().x][delta.getCell().y], attacker);
    source = map[delta.getSource().x][delta.getSource().y];
    dest = map[delta.getDestination().x][delta.getDestination().y];
    distanceX =  dest.getCenter().x -
            source.getCenter().x;
    distanceY = dest.getCenter().y -
            source.getCenter().y;
    if (counter < frames / 2){
        g2d.drawImage(ImageHolder.attacker, source.getBounds().x + (int)(((float)counter/frames) * distanceX),
                source.getBounds().y + (int)(((float)counter/frames) * distanceY),
                null);
    }
    else{
        g2d.drawImage(ImageHolder.attacker, dest.getBounds().x - (int)(((float)counter/frames) * distanceX),
                dest.getBounds().y - (int)(((float)counter/frames) * distanceY),
                null);
    }
    break;

I want each loop takes, for example, exactly 200 miliseconds. How can I achieve this?

Upvotes: 0

Views: 91

Answers (2)

Marco13
Marco13

Reputation: 54709

Probably not an acceptable answer, but too long for a comment:

There are several options, depending on the actual intention. The pattern that you described is not uncommon for a "simple game loop". In this case, a code that is similar to the one that you posted is run in an own thread, and regularly triggers a repaint() in order to paint the updated game state. In your case, it seems that only the animationFrame variable is increased. For such a simple action, the alternatives that have already been mentioned may be sufficient:

  • As suggested by whiskeyspider in https://stackoverflow.com/a/21860250/ : You could use a java.util.Timer with a TimerTask that only updated the animationFrame
  • Alternatively, you could use a javax.swing.Timer whose ActionListener updates the animationFrame. This might be advantageous here, because you can be sure that the update of the animationFrame happens on the event dispatch thread. Thus, the update of this variable can not interfere with its usage in the painting method, for example
  • But as mentioned in the comments: An own thread that executed the code that you already posted (with the minor adaptions) is also feasible and not "wrong" per se - but note that in this case (similar to when using a java.util.Timer) you might have to take care of the synchronization on your own

EDIT: Based on the edited question: Similar to what I expected, you are using the animationFrame in your painting method. The crucial point here are the details about how the animationFrame variable is used. For example, if your paintComponent method looks like this

class MyJPanel extends JPanel {
    private int animationFrame = 0;

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g); 

        drawChanges(g, getDelta(), animationFrame, FJPanle.FRAMES);
        drawSomethingElse(g, animationFrame, ...);
        drawEvenMore(g, animationFrame, ...);
    }

    ...
}

then it may happen that the value of animationFrame is changed (by another thread, possibly the java.util.Timer thread) while the paintComponent method is executed. That means that drawChanges and drawSomethingElse may receive different values for animationFrame. This may cause rendering artefacts (misplaced images, tearing between tiles etc).

This could either be avoided by using a javax.swing.Timer (because then, the updates of animationFrame will be done on the same thread as the one that executes paintComponent), or by making sure that all painting operations use the same value of animationFrame - roughly like this:

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g); 

        int animationFrameUsedForAllPainting = animationFrame;
        drawChanges(g, getDelta(), animationFrameUsedForAllPainting , FJPanle.FRAMES);
        drawSomethingElse(g, animationFrameUsedForAllPainting , ...);
        drawEvenMore(g, animationFrameUsedForAllPainting , ...);
    }

But apart from that, there is not sooo much difference between the aforementioned approaches in this case. So for simplicity, you could use the javax.swing.Timer (or the java.util.Timer when you make sure that the update is "thread safe" regarding the painting operations).

Upvotes: 1

martinez314
martinez314

Reputation: 12332

Look into using a Timer. For example, the scheduleAtFixedRate() method.

Upvotes: 2

Related Questions