kattapillar
kattapillar

Reputation: 136

Moving Object along a path: How to call actionPerformed only after previous actionPerformed has finished?

I have a question about Swing Timer, specifically how to call actionPerformed a number of times and waiting for the last actionPerformed to finish before executing it again. I know other people have asked this before, but none of the solutions I found work for my program.

Heres what I have: I am trying to use Square objects (Rectangles) along a path from one position to another. The path is given as an array of square objects (path is discovered using A*, all the squares in the path are added to an ArrayList). The actionPerformed method moves the square from its current x, y position to the next, and next is as I mentioned the next square in the path. So for example:

Start (the moving square) is at x 200, y 50 End (the target square) is at x 300, y 150 The squares in the path to this position are (in x, y position): (250, 50), (300, 50), (300, 100), (300, 150)

enter image description here

But instead of moving one square at a time, the moving square will move as seen below: (taking a shortcut).

enter image description here

I believe the reason is that the timer is not waiting for actionPerformed to finish, so before the square even started moving, the listener has already received the last square (300, 150) as the target, and then the moving square moves there directly..

Here's some of my code:

MovementListener (holds actionPerformed)

public MovementListener(Canvas canvas){
        this.canvas = canvas;
    }

public void setSquares(Square moving, Square target){
    this.moving = moving;
    this.target = target;
}

@Override
public void actionPerformed(ActionEvent e) {
    // I tried adding a boolean here, to indicate if the action is finished or not, what I tried was:
    // setRunning(true); (set to false below, see at the end of this method)
    if (this.moving.getX() < this.target.getX()){
        moving.setLocation((int)(moving.getX() + 10), (int)(moving.getY()));
        this.canvas.repaint();
    } 

    if (this.moving.getY() < this.target.getY()){
        moving.setLocation((int)(moving.getX()), (int)(moving.getY() + 10));
        this.canvas.repaint();
    }
    // setRunning(false);
}

Here's the class that holds the listener and timer:

public Canvas(){
    // setup ActionListener
    listener = new MovementListener(this);
    timer = new Timer(100, listener);
}


public void startTimer(Square moving, Square target){ 
    // I tried using the isRunning variable here, so that the next actionPerformed doesn't get called unless listener is ready
    if (listener.isReady()){
        listener.setSquares(moving, target);
        timer.start();
    }
}

And here's the code where the moving action starts (the Square moving is defined above). Also, the backwards for-loop is correct, the path ArrayList is in reverse order (last square at position 0, beginning of path at the end of the list):

for (int i = controller.getPath().size()-1; i >= 0; i--){
     Square target = controller.getPath().get(i);
     canvas.startTimer(moving, target);
}

Any help would be greatly appreciated! I've been trying to figure this out for hours now.. Thanks :)

EDIT: Okay I got it to work doing it the way camickr described below:

the new actionPerformed

@Override
public void actionPerformed(ActionEvent e) {
    Square current = controller.getPath().get(0);
    if (this.moving.getX() < current.getX()){
        moving.setLocation((int)(moving.getX() + 10), (int)(moving.getY()));
        this.canvas.repaint();
    } 

    if (this.moving.getY() < current.getY()){
        moving.setLocation((int)(moving.getX()), (int)(moving.getY() + 10));
        this.canvas.repaint();
    } else {
        controller.getPath().remove(current);
    }

    if (controller.getPath().isEmpty()){
        canvas.getTimer().stop();
    }
}

This is now working!

Upvotes: 0

Views: 237

Answers (1)

camickr
camickr

Reputation: 324157

for (int i = controller.getPath().size()-1; i >= 0; i--){
     Square target = controller.getPath().get(i);
     canvas.startTimer(moving, target);
}

In your code above you iterate through the loop setting the Square position. The square position is set to the end before the Timer even has a chance to fire, so you only ever see the square painted in the last position.

When you use a Timer you don't need to use a loop. The Timer replaces the loop. You simply start the Timer. Then when the Timer fires you do something.

So, your logic would be something like:

  1. Add the Square objects to the ArrayList
  2. Start the Timer
  3. When the ActionListener is invoked you a) get the first Square from the ArrayList b) move the object to the square c) remove the Square from the ArrayList
  4. When the ArrayList is empty you stop the Timer.

Upvotes: 1

Related Questions