I am trying to get a simple fast forward button to work. My understanding is initializing a timer as such
timer = new Timer(setspeed, listener);
sets the delay between timer events to the int setspeed in milliseconds.
i have a fast forward button that has the following code:
public void doFastForward()
{
speedcounter++;
setspeed = speed / speedcounter;
System.out.print(speedcounter + " " + setspeed + ". "); //checker
timer.stop();
timer.setDelay((setspeed));
timer.start();
System.out.print(timer.getDelay() + ".. "); //checker
}
which is supposed to cut the speed by half, third, fourth, etc, with every button press. Issue is its totally not doing that with my simulation. Is there something I'm missing here?
timer.setDelay() works for me. Here's a quick example that moves a red square across the screen. Pressing the fast-forward button makes it move faster by calling setDelay on the timer (you'll notice the logic in my ActionListener is identical to yours, albeit different variable names):
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class TimerTest extends JFrame {
public static void main(String[] args) { new TimerTest().setVisible(true); }
private static final int DEFAULT_SPEED = 500;
private int speedCounter = 1;
private int currentSpeed = DEFAULT_SPEED / speedCounter;
private int squareX = 150;
public TimerTest() {
super("Test");
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
setMinimumSize(new Dimension(300, 200));
setLayout(new BorderLayout());
setLocationRelativeTo(null);
JPanel displayPanel = new JPanel(null) {
#Override
public void paintComponent(Graphics g) {
g.setColor(Color.RED);
g.fillRect(squareX, getHeight() / 2, 25, 25);
}
};
final Timer timer = new Timer(currentSpeed, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
squareX = (squareX + 15) % getWidth();
repaint();
}
});
timer.setRepeats(true);
JButton fastForwardButton = new JButton(new AbstractAction(">>") {
#Override
public void actionPerformed(ActionEvent e) {
speedCounter++;
currentSpeed = DEFAULT_SPEED / speedCounter;
timer.stop();
timer.setDelay(currentSpeed);
timer.start();
}
});
add(displayPanel, BorderLayout.CENTER);
add(fastForwardButton, BorderLayout.SOUTH);
timer.start();
}
}
One noticeable flaw with this approach is that starting/stopping the timer will cause an additional delay. For example, say the timer was set to fire every 2 seconds and it has been 1.5 seconds since the last frame. If you set the delay to 1 second and restart the timer, 2.5 seconds will have passed before the next frame fires. This could explain why "it didn't work" for you.
To avoid this issue, you could create a timer that fires at some fixed rate but only executes your logic if enough time has passed. Then change what "enough time" is when the user fast-forwards. For example, the timer could fire every 50ms, but you only execute your logic if 500ms has passed since the last execution. When the user fast-forwards, you could then execute your logic if 250ms has passed instead (and so on).
Related
I'm actually trying to fix a problem with the component of JFrame that don't want to show up when I run my game loop (see the question after the code). I have reduced the code at the minimum possible for you to get what I mean fast:
Run.class
public static void main(String[] args) {
javax.swing.SwingUtilities.invokeLater(new Runnable() {
public void run() {
Game game = new Game();
game.loop();
}
});
}
Game.class
private Frame frame;
private HashMap<String,ActionListener> actions;
private HashMap<String,Image> ressources;
public Game() {
this.setActions();
this.setRessources();
frame = new Frame(actions,ressources);
}
public void loop() {
double FPS = 60;
double UPS = 60;
long initialTime = System.nanoTime();
final double timeU = 1000000000 / UPS;
final double timeF = 1000000000 / FPS;
double deltaU = 0, deltaF = 0;
int frames = 0, ticks = 0;
long timer = System.currentTimeMillis();
boolean running = true;
boolean RENDER_TIME = false;
while (running) {
...code for update, render, with a fps control
}
}
Frame.class
public Frame(HashMap<String,ActionListener> actions, HashMap<String,Image> ressources) {
this.setTitle(Constants.GAME_NAME);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setSize(Constants.GAME_SIZE_X, Constants.GAME_SIZE_Y);
this.setLayout(new FlowLayout());
JButton myButton = new JButton("My Button");
this.add(myButton);
this.revalidate();
this.repaint();
this.setVisible(true);
}
It's not the complete code because I don't want to give a useless thing. So here, my problem is:
If I run this code, the button not going to show up in the frame. But if I comment game.loop() in the Run.class, the windows show the button. And I don't understand WHY?
I have been trying for a few days to figure it out. I need some help for this one. Alone I'm afraid I will not find out.
To avid blocking the Event Dispatching Thread by running a long process you can use swing Timer which can handle the "looping" for you :
ActionListener animate = e -> {
game.oneFrame();
panel.repaint();
};
timer = new Timer(50, animate);
timer.start();
public void oneFrame(){
//update what is needed for one "frame"
}
For more help post mcve.
I'm creating a game that ends if you haven't reached a certain point by the time the timer reaches zero. It currently works and I can create multiple TimerTasks at one time, but I cannot cancel them. Currently, clicking the button resets the timer (on the display) but will still run in the background and end the program when it reaches zero (even though it shouldn't be running). Here's the code for the ActionListener that starts each timer.
public class Game implements Runnable {
private int currentScore, difficulty, level, highscore, x, y;
private boolean playMusic, playClicks, gameRunning;
private boolean stopLastTimer = false;
JFrame gameFrame;
Data data;
JButton target;
Thread t;
JLabel score;
Timer globalTimer = new Timer();
JLabel timer;
ActionListener clickedAction = new ActionListener(){
public void actionPerformed(ActionEvent ae) {
stopLastTimer = true;
t.interrupt();
currentScore++;
score.setText("Score: " + currentScore);
//playClickSound();
globalTimer.schedule(new TimerTask(){
double second = 2.0;
#Override
public void run() {
second = second - 0.1;
if(stopLastTimer) {
this.cancel(); stopLastTimer = false; } //should end old timer here
if(second == 0.0) {
this.cancel();
gameStop();
}
second = limitPrecision(Double.toString(second), 1);
timer.setText(second + "s");
}
},0, 100);
}
};
Don't use a TimerTask.
Instead you should be using a javax.swing.Timer for the animation. A Swing Timer has a stop() method.
The source of the ActionEvent will be the Timer itself.
Read the section from the Swing tutorial on How to Use Timer for more information and working examples.
I have a simple Java/Swing application that tries to animate a box by moving it from left to right:
public class TestFrame extends JFrame {
protected long startTime = new Date().getTime();
public class MainPanel extends JPanel {
#Override
protected void paintComponent(Graphics g) {
// calculate elapsed time
long currentTime = new Date().getTime();
long timeDiff = currentTime - TestFrame.this.startTime;
// animation time dependent
g.fillRect((int) (timeDiff / 100), 10, 10, 10);
}
}
public class MainLoop implements Runnable {
#Override
public void run() {
while (true) {
// trigger repaint
TestFrame.this.repaint();
}
}
}
public static void main(String[] args) {
new TestFrame();
}
public TestFrame() {
// init window
this.setTitle("Test");
this.setSize(500, 500);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.add(new MainPanel());
this.setVisible(true);
// start render loop
Thread loop = new Thread(new MainLoop());
loop.start();
}
}
The problem is that the animation is not clean and the box jumps (sometimes) a few pixels. I already did some researches and according to them it should work fine if using paintComponent (instead of paint) and doing a time based animation (not frame based). I did both but the animation is still not clean.
Could anybody give me a hint what is going wrong?
You should give your while-true-loop a little rest. You're kind of burning your CPU! You're generating a tremendous amount of paint events; at some time which the thread scheduler decides, the scheduler hands off to the event dispatching thread, which as far as I recall may collapse your trillon of paint events into a single one and eventually execute paintComponent.
In the following example, the thread sleeps 20ms, which gives you a maximum frame rate of 50fps. That should be enough.
while (true) {
// trigger repaint
TestFrame.this.repaint();
try {
Thread.sleep(20);
} catch(InterruptedException exc() {
}
}
I made a few changes to your code.
I called the SwingUtilities invokeLater method to create and use your Swing components on the Event Dispatch thread.
I called the System currentTimeinMillis method to get the current time.
Instead of setting the JFrame size, I set the size of the JPanel and packed the JFrame. I reduced the size of the JPanel to speed up the repainting.
I added a delay in the while(true) loop, as fjf2002 suggested in his answer.
Here's the revised and formatted code:
package com.ggl.testing;
import java.awt.Dimension;
import java.awt.Graphics;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class TestFrame extends JFrame {
private static final long serialVersionUID = 272649179566160531L;
protected long startTime = System.currentTimeMillis();
public class MainPanel extends JPanel {
private static final long serialVersionUID = 5312139184947337026L;
public MainPanel() {
this.setPreferredSize(new Dimension(500, 30));
}
#Override
protected void paintComponent(Graphics g) {
// calculate elapsed time
long currentTime = System.currentTimeMillis();
long timeDiff = currentTime - TestFrame.this.startTime;
// animation time dependent
g.fillRect((int) (timeDiff / 100), 10, 10, 10);
}
}
public class MainLoop implements Runnable {
#Override
public void run() {
while (true) {
// trigger repaint
TestFrame.this.repaint();
try {
Thread.sleep(20L);
} catch (InterruptedException e) {
}
}
}
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new TestFrame();
}
});
}
public TestFrame() {
// init window
this.setTitle("Test");
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.add(new MainPanel());
this.pack();
this.setVisible(true);
// start render loop
Thread loop = new Thread(new MainLoop());
loop.start();
}
}
Your painting on my machine is rather smooth but it is burning a lot of performance in that loop, but on a slower machine I could imagine that the animation is jumpy if your application is busy executing the while or handling the paint events instead of rendering.
It may be better to update the position based on how much time has elapsed per render, and I am unsure how accurate it is to compare times for animation purposes through Date objects, so comparing small time differences using System.nanoTime() may be better. For example:
long currentNanoTime = System.nanoTime();
long timeElapsed = currentNanoTime - lastUpdateNanoTime;
lastUpdateNanoTime = currentNanoTime;
...
int newXPosition = oldXPosition + (velocityXInPixelsPerNanoSecond * timeElapsed);
I'm trying to use a timer to change the position of a JLabel, from one spot on my JPanel to another. I'm not sure if I could use say .getLocation(), then change only the horizontal x value, and finally use .setLocation() to effectively modify the JLabel. I've also used .getBounds and .setBounds, but am still unsure how I can obtain the old horizontal x value to change and reapply to the new x value.
The code I tried looks something like this, but neither is a valid way to change the position of the JLabel.
// mPos is an arraylist of JLabels to be moved.
for(int m = 0; m < mPos.size(); m++){
mPos.get(m).setLocation(getLocation()-100);
}
or
for(int m = 0; m < mPos.size(); m++){
mPos.get(m).setBounds(mPos.get(m).getBounds()-100);
}
If I could just get the position of the horizontal x value I can change the position of the label.
Try with Swing Timer if you are looking for some animation.
Please have a look at How to Use Swing Timers
Here is the sample code:
int delay = 1000; //milliseconds
ActionListener taskPerformer = new ActionListener() {
public void actionPerformed(ActionEvent evt) {
//...Perform a task...
}
};
new Timer(delay, taskPerformer).start();
Find a Sample code here
Sample code: (Move Hello World message 10px horizontally left to right at interval of 200 ms)
private int x = 10;
...
final JPanel panel = new JPanel() {
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawString("Hello World", x, 10);
}
};
int delay = 200; // milliseconds
ActionListener taskPerformer = new ActionListener() {
public void actionPerformed(ActionEvent evt) {
x += 10;
if (x > 100) {
x = 10;
}
panel.repaint();
}
};
new Timer(delay, taskPerformer).start();
I made a similar example just so you can get the basic jest of it, try copy pasting this in a new class called "LabelPlay" and it should work fine.
import java.awt.EventQueue;
import java.util.Random;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JButton;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
public class LabelPlay {
private JFrame frame;
private JLabel label;
private Random rand;
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
public void run() {
try {
LabelPlay window = new LabelPlay();
window.frame.setVisible(true);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
public LabelPlay() {
initialize();
}
private void initialize() {
frame = new JFrame();
frame.setBounds(100, 100, 659, 518);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().setLayout(null);
label = new JLabel("YEEEHAH!");
label.setBounds(101, 62, 54, 21);
frame.getContentPane().add(label);
JButton btnAction = new JButton("Action!");
rand = new Random();
btnAction.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent arg0) {
int a = rand.nextInt(90)+10;
int b = rand.nextInt(90)+10;
int c = rand.nextInt(640)+10;
int d = rand.nextInt(500)+10;
label.setBounds(a, b, c, d);
}
});
btnAction.setBounds(524, 427, 89, 23);
frame.getContentPane().add(btnAction);
}
}
If you want this to happen in a loop at certain times, you can just put it in a loop and use Thread.sleep(amount of miliseconds) in the loop before you run the code.
Why don't you create a JLabel at position a, set it to visible, and another one at position b, setting it to not visible? After the timer is up, hide the first and show the second.
Are you planning on creating some moving imageIcon for some type of game? Or some label that moves pretty much everywhere ?
I would use an Absolute Layout and set Location manually everytime.
myPanel.setLayout(null);
// an initial point
int x = 100;
int y =100;
while (
//some moving pattern
x++; // 1 pixel per loop
y+=2; // 2 pixels per loop
myLabel.setLocation(x,y);
}
I have problem that I don't understand, how to correctly use the Java timer with JButton.
The idea of what I need -
When I click on JButton with text "0" then starts the timer counting from two seconds till zero.
When button is released program checks the situation:
if timer now is 0 then it shows in JTextField sign "+", else it shows "0".
Here is my code of program. Can someone please add the things that I need to make the program work like the idea I want?
public class DialPanel extends JPanel {
private MainFrame frame;
public DialPanel(MainFrame frame) {
this.frame = frame;
this.setLocation(0, 90);
this.setSize(300, 290);
this.setLayout(null);
this.setBackground(color);
this.initContent();
}
// -------------------------------------------------------------------------
private JButton btnNumZero;
private JTextField txfNumber;
private void initContent() {
txfNumber = new JTextField();
this.add(txfNumber);
txfNumber.setSize(190, 30);
txfNumber.setLocation(30, 0);
txfNumber.setFocusable(false);
txfNumber.addActionListener(controller);
btnNumZero = new JButton();
this.add(btnNumZero);
btnNumZero.setText("0");
btnNumZero.setFocusable(false);
btnNumZero.setSize(30, 30);
btnNumZero.setLocation(10, 10);
btnNumZero.addMouseListener(new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent e) {
//Start someTimer countdown from two seconds
}
});
btnNumZero.addMouseListener(new MouseAdapter() {
#Override
public void mouseReleased(MouseEvent e) {
//Stop someTimer
//if someTimer == 0 seconds then do this line:
txfNumber.setText("+");
//else do this line:
txfNumber.setText("0");
}
});
}
}
Excuse me if there is some unnecessary error with code. I deleted and changed a lot of things from the real one code so that this could be more understandable and clear for reading.
Instead of using a Timer, you may want to just make use of System.currentTimeMillis(). The Timer may not be a timer in the sense you are looking for, as a stopwatch type object.
You could do something like this
long startTime;
long endTime;
btnNumZero.addMouseListener(new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent e) {
startTime = System.currentTimeMillis();
}
});
btnNumZero.addMouseListener(new MouseAdapter() {
#Override
public void mouseReleased(MouseEvent e) {
endTime = System.currentTimeMillis();
long difference = endTime - startTime;
if (difference > 2000)
txfNumber.setText("+");
else
txfNumber.setText("0");
}
});