e-barnett
e-barnett

Reputation: 75

Multiple swing timers

I have (what should be) a simple problem to tackle and I'm open to other ways to solve it. I am open to other solutions.

The problem: We are using java swing to display the graphics of a turn-based, tile-based game. I'm using jlabels with icons, absolutely positioned.

To animate the movement, I am using a swing timer that updates the location by 4 pixels at a time, slowly moving the sprite right, left, etc.

To achieve this initially, I was running a timer, which works wonderfully. The problem comes in when I try to move down, then move right.

The sprite moves down, never moves right, and if I watch the execution with some console printing, it's clear to see that both timers are running at the same time. I've done a fair amount of digging on the internet and I wasn't able to find a way to tell a swing timer not to execute until the first timer has stopped, and if I try to busy-wait until one timer finishes (yuck) the UI never displays at all (clearly a step in the wrong direction.)

Now I can convert away from timers altogether and either have the sprite teleport to its new location, or use some awful busy-wait movement scheme, but I'm hoping some kind soul has a solution.

In short: I need a way to run a swing timer for a set period of time, stop it, and then start a new timer, so that they do not overlap. Preferably this method would allow each timer to be in its own method, and I could then call the methods one after the other.

Thanks in advance for any advice you might have.

Edit: Expanded example code. If a full scsse is a requirement for your advice then I'm sorry to have wasted your time, because the full code is a beast. This sample code does not work at all as it stands, sorry, but it should illustrate the point.

So. We have two functions, each with a timer that runs an animation cycle, one for moving down and right diagonally, one for moving straight down.

public class TestClass {
    static int counter = 0;
    static int counter2 = 0;
    static Timer timerC;
    static Timer timerX;

    public static void main(String[] args) {
        moveC();
        moveX();
    }

    public static void moveC() {
        int delay = 200; // milliseconds

        timerC = new Timer(delay, null);
        timerC.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent evt) {
                if (counter < 32) {
                    counter = counter + 4;
                    System.out.println("*C*");
                } else {
                    timerC.stop();
                    System.out.println("*C STOP*");
                }

            }
        });
        timerC.start();
    }

    public static void moveX() {
        int delay = 200; // milliseconds

        timerX = new Timer(delay, null);
        timerX.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent evt) {
                if (counter < 32) {
                    counter = counter + 4;
                    System.out.println("*X*");
                } else {
                    timerX.stop();
                    System.out.println("*X STOP*");
                }

            }
        });
        timerX.start();
    }

}

What I would want to see here eventually would be

*C*
*C*
*C*
*C*
*C STOP*
*X*
*X*
*X*
*X*
*X STOP*

What I actually get is

*C*
*X*
*C*
*X*
*C*
*X*
*C*
*X*
*C STOP*
*X STOP*

The point I'm trying to get at here is running one animation cycle to completion, then the other.

Thanks again.

Upvotes: 3

Views: 9158

Answers (3)

Hovercraft Full Of Eels
Hovercraft Full Of Eels

Reputation: 285405

Don't use multiple Timers, but rather only one Timer that deals with each direction as it's needed. You need some type of queue to hold the direction information, either a formal queue or a collection that you use as a queue (first in, first out), and then have your Timer extract the direction from this queue as it's running. For example, here I use my JList's model as my queue by removing and using the Direction that was added first (at the top of the JList):

import java.awt.GridLayout;
import java.awt.event.*;
import javax.swing.*;

public class TimerPlay extends JPanel {
   private DefaultListModel directionJListModel = new DefaultListModel();
   private JList directionJList = new JList(directionJListModel);
   JButton startTimerButton = new JButton(
         new StartTimerBtnAction("Start Timer"));

   public TimerPlay() {
      ActionListener directionBtnListener = new ActionListener() {

         @Override
         public void actionPerformed(ActionEvent actEvt) {
            String actionCommand = actEvt.getActionCommand();
            Direction dir = Direction.valueOf(actionCommand);
            if (dir != null) {
               directionJListModel.addElement(dir);
            }
         }
      };
      JPanel directionBtnPanel = new JPanel(new GridLayout(0, 1, 0, 10));
      for (Direction dir : Direction.values()) {
         JButton dirBtn = new JButton(dir.toString());
         dirBtn.addActionListener(directionBtnListener);
         directionBtnPanel.add(dirBtn);
      }

      add(directionBtnPanel);
      add(new JScrollPane(directionJList));
      add(startTimerButton);
   }

   private class StartTimerBtnAction extends AbstractAction {
      protected static final int MAX_COUNT = 20;

      public StartTimerBtnAction(String title) {
         super(title);
      }

      @Override
      public void actionPerformed(ActionEvent arg0) {
         startTimerButton.setEnabled(false);

         int delay = 100;
         new Timer(delay, new ActionListener() {
            private int count = 0;
            private Direction dir = null;

            @Override
            public void actionPerformed(ActionEvent e) {
               if (count == MAX_COUNT) {
                  count = 0; // restart
                  return;
               } else if (count == 0) {
                  if (directionJListModel.size() == 0) {
                     ((Timer)e.getSource()).stop();
                     startTimerButton.setEnabled(true);
                     return;
                  }
                  // extract from "queue"
                  dir = (Direction) directionJListModel.remove(0);
               }
               System.out.println(dir); // do movement here
               count++;

            }
         }).start();
      }
   }

   private static void createAndShowGui() {
      TimerPlay mainPanel = new TimerPlay();

      JFrame frame = new JFrame("TimerPlay");
      frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
      frame.getContentPane().add(mainPanel);
      frame.pack();
      frame.setLocationByPlatform(true);
      frame.setVisible(true);
   }

   public static void main(String[] args) {
      SwingUtilities.invokeLater(new Runnable() {
         public void run() {
            createAndShowGui();
         }
      });
   }

}

enum Direction {
   UP, DOWN, LEFT, RIGHT;
}

Upvotes: 6

mKorbel
mKorbel

Reputation: 109815

  • put all Icons in some form of array

  • create a single Swing Timer with a short delay

  • in Swing ActionListener, take each `Icon from the array, getBounds from screen, move Icon one step

  • repeat until target reached.

Upvotes: 5

trashgod
trashgod

Reputation: 205775

For reference, this example manages four instances of Timer, two of which run (interleaved) while hovering in any corner. You might compare it to your approach. This related answer discusses animation in a similar tile-based game.

Upvotes: 5

Related Questions