Reputation: 75
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
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
Reputation: 109815
put all Icon
s 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