I'm trying to update a label every millisecond in libGDX for a set amount of time.
However, sometimes the label suddenly stops without an error OR I receive a "String index out of range" error which then crashes my program. These are two separate issues.
Code:
Stage stage;
Timer timer = new Timer();
Label countUpLabel;
int countUp;
#Override
public void create () {
stage = new Stage(new FitViewport(Gdx.graphics.getWidth(), Gdx.graphics.getHeight()));
//Setting up time label
BitmapFont font = new BitmapFont(Gdx.files.internal("04b_19__-32.fnt"));
LabelStyle labelStyle = new LabelStyle(font, Color.WHITE);
countUpLabel = new Label("Time:\n00000", labelStyle);
countUpLabel.setPosition(200,200);
countUpLabel.setAlignment(Align.center);
countUpLabel.setFontScale(3);
stage.addActor(countUpLabel);
//Setting up timer
timer.schedule(new TimerTask() {
#Override
public void run() {
if (countUp < 3000) {
countUp++;
countUpLabel.setText(String.format("Time:\n%d", countUp));
}else
timer.cancel();
}
}, 0, 1); //every millisecond run the timer
}
Thanks in advance.
Most of libGDX is not thread safe, unless explicitly stated that it is. Also, updating a label every millisecond, while it is only shown about every 16 milliseconds (the refresh rate of your typical display device), isn't a very good approach. So you might want to remove your timer and instead update the label when it is actually visible: in the render method:
float counter = 0f;
#Override
public void render() {
if (counter < 3) {
counter += Gdx.graphics.getDeltaTime();
countUpLabel.setText(String.format("Time:\n%01.22f", counter));
}
// call glClear, stage.act, stage.draw, etc.
}
Related
I'm building app for making tests. I have a scene made in SceneBuilder. There is a ImageView under image Label with Question and 3 buttons "A", "B", "C", Texts to buttons and question's Label are taken from DataBase, if you click answer new Image, question and asnwers are loading, everything works, but i want to add a Timer in the corner. When image and question show on the screen there will be "Time to read the question" and countdown from 10 to 0, and then "Time to answer" and again countdown from 10 to 0. If timer ends and there is no answer question and image will change automaticly. But the problem is that, i can do the timer, its counting down, and after this time it change the question but I dont know how to put it into Label. If inside Timer I do something like seconds-- label.setText(seconds) there is no error but when I start app there is a lot of exceptions. Can you help me how I can put this variable which is decrementing in timer after each second to Label ?
public void setTimer() {
timer = new Timer();
timer.scheduleAtFixedRate(new TimerTask() {
public void run() {
if(interval > 0)
{
timeElapsed.setText("Time to read the question: "+interval);
System.out.println(interval);
interval--;
}
else
timer.cancel();
}
}, 1000,1000);
}
Now i have something like this, in console everything is working there is a countdown from 10 to 0 but no effect in scene.
And errors:
Exception in thread "Timer-0" java.lang.IllegalStateException: Not on FX application thread; currentThread = Timer-0
at javafx.graphics/com.sun.javafx.tk.Toolkit.checkFxUserThread(Toolkit.java:291)
at javafx.graphics/com.sun.javafx.tk.quantum.QuantumToolkit.checkFxUserThread(QuantumToolkit.java:423)
at javafx.graphics/javafx.scene.Parent$3.onProposedChange(Parent.java:493)
at javafx.base/com.sun.javafx.collections.VetoableListDecorator.setAll(VetoableListDecorator.java:113)
at javafx.base/com.sun.javafx.collections.VetoableListDecorator.setAll(VetoableListDecorator.java:108)
at javafx.controls/javafx.scene.control.skin.LabeledSkinBase.updateChildren(LabeledSkinBase.java:271)
at javafx.controls/javafx.scene.control.skin.LabeledSkinBase.lambda$new$11(LabeledSkinBase.java:219)
at javafx.controls/com.sun.javafx.scene.control.LambdaMultiplePropertyChangeListenerHandler.lambda$new$1(LambdaMultiplePropertyChangeListenerHandler.java:49)
at javafx.base/javafx.beans.value.WeakChangeListener.changed(WeakChangeListener.java:89)
at javafx.base/com.sun.javafx.binding.ExpressionHelper$SingleChange.fireValueChangedEvent(ExpressionHelper.java:181)
at javafx.base/com.sun.javafx.binding.ExpressionHelper.fireValueChangedEvent(ExpressionHelper.java:80)
at javafx.base/javafx.beans.property.StringPropertyBase.fireValueChangedEvent(StringPropertyBase.java:104)
at javafx.base/javafx.beans.property.StringPropertyBase.markInvalid(StringPropertyBase.java:111)
at javafx.base/javafx.beans.property.StringPropertyBase.set(StringPropertyBase.java:145)
at javafx.base/javafx.beans.property.StringPropertyBase.set(StringPropertyBase.java:50)
at javafx.base/javafx.beans.property.StringProperty.setValue(StringProperty.java:65)
at javafx.controls/javafx.scene.control.Labeled.setText(Labeled.java:147)
at gui.controller.StudentTestYesNoController$1.run(StudentTestYesNoController.java:40)
at java.base/java.util.TimerThread.mainLoop(Timer.java:556)
at java.base/java.util.TimerThread.run(Timer.java:506)
The problem is that you are trying to change the UI from a thread other than the application.
This should solve the problems with your current implementation
public void setTimer() {
timer = new Timer();
timer.scheduleAtFixedRate(new TimerTask() {
public void run() {
if(interval > 0)
{
Platform.runLater(() -> timeElapsed.setText("Time to read the question: "+interval));
System.out.println(interval);
interval--;
}
else
timer.cancel();
}
}, 1000,1000);
}
Also, you can take a look at something specific about the javafx - Timeline
JavaFX periodic background task
Adding on to what mr mcwolf said above, i think you need to set
Platform.setImplicitExit(false);
before Platform.runLater(); so that it will run every time the task is called.
I am trying to write a simple little thing that stops an audio file from being played when the user "interrupts" it by talking to the phone. I'm using the SoundMeter class that this person wrote to get the maxAmplitude of the microphone. I've set a threshold to 800 and want to test the return of the microphone amplitude against it constantly (every 50ms).
For some reason, it's hit or miss when I get the "interrupt" to show up and when the audio stops. I'd like for the interrupt to be showing the entire time the amplitude is above 800.
handler = new Handler();
final Runnable r = new Runnable() {
#Override
public void run() {
mic_amplitude.setText(""+mSoundMeter.getAmplitude());
if(mSoundMeter.getAmplitude() > threshold) {
Log.d("INTERRUPT", "INTERRUPT");
interrupt.setText("Interrupt");
mBackgroundAudio.pause(mp);
audioButton.setText("Resume Audio");
} else {
interrupt.setText("");
}
handler.postDelayed(this, 50);
}
};
handler.postDelayed(r, 50);
While viewing this, I'm able to see the amplitude at a steady 40-50 while there is no background noise and peaks of 1200-1500 when talking quietly. I'd like the interrupt to show anytime above 800, but it's currently only showing up intermittently.
Ok, I figured it out. I tested what my amplitude was by logging the amp along with the interrupt and I saw I was getting 0. I realized I had been testing on a new amplitude (other than the amp I was showing), so I assigned the amplitude to a variable and used the variable everywhere else.
here's my outcome:
handler = new Handler();
final Runnable r = new Runnable() {
#Override
public void run() {
mAmplitude = mSoundMeter.getAmplitude();
mic_amplitude.setText(""+mAmplitude);
if(mAmplitude > threshold) {
Log.d("INTERRUPT", "INTERRUPT " + mAmplitude);
interrupt.setText("Interrupt");
mBackgroundAudio.pause(mp);
audioButton.setText("Resume Audio");
} else {
interrupt.setText("");
}
handler.postDelayed(this, 100);
}
};
handler.postDelayed(r, 100);
For effect, I want a label to display time like a stop watch would. The time starts at 0, and ends somewhere around 2 or 3 seconds. The service is stopped when desired. The issue I am having, is because I am trying to update the text 1000 times per second, the timer is lagging behind.
Like I said, this is for effect and will be hidden as soon as the timer stops, but the time should be fairly accurate, not 3 seconds behind.
Is there any way I can make this faster? I would like to have all 4 decimal places if possible.
timer = new Label();
DoubleProperty time = new SimpleDoubleProperty(0.0);
timer.textProperty().bind(Bindings.createStringBinding(() -> {
return MessageFormat.format(gui.getResourceBundle().getString("ui.gui.derbydisplay.timer"), time.get());
}, time));
Service<Void> countUpService = new Service<Void>() {
#Override
protected Task<Void> createTask() {
return new Task<Void>() {
#Override
protected Void call() throws Exception {
while (!isCancelled()) {
Platform.runLater(() -> time.set(time.get() + 0.0001));
Thread.sleep(1);
}
return null;
}
};
}
};
The main reason your clock lags is because you increase the time by 0.0001 seconds (i.e. 1/10000 seconds) 1000 times per second. So every second it will increase by 0.1... But even if you fix that issue, it would still lag a small amount because you don't account for the time it takes to make the method calls. You can fix this by checking the system clock when you start, and then checking it every time you do the update.
Updating the label 1000 times per second is pretty much redundant, because JavaFX only aims to update the UI 60 times per second. (It will be slower if the UI thread is busy trying to do too much stuff, which you also make happen by scheduling so many calls to Platform.runLater().) You can fix this by using an AnimationTimer. The AnimationTimer.handle(...) method is called once every time a frame is rendered, so this effectively updates as often as JavaFX allows the UI to update.
AnimationTimer timer = new AnimationTimer() {
private long startTime ;
#Override
public void start() {
startTime = System.currentTimeMillis();
super.start();
}
#Override
public void handle(long timestamp) {
long now = System.currentTimeMillis();
time.set((now - startTime) / 1000.0);
}
};
You can start this with timer.start(); and stop it with timer.stop();. Obviously you can add more functionality to set a time for it to run, etc, and call stop() from the handle(...) method if you need.
SSCEE:
import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class Timer extends Application {
#Override
public void start(Stage primaryStage) {
Label label = new Label();
DoubleProperty time = new SimpleDoubleProperty();
label.textProperty().bind(time.asString("%.3f seconds"));
BooleanProperty running = new SimpleBooleanProperty();
AnimationTimer timer = new AnimationTimer() {
private long startTime ;
#Override
public void start() {
startTime = System.currentTimeMillis();
running.set(true);
super.start();
}
#Override
public void stop() {
running.set(false);
super.stop();
}
#Override
public void handle(long timestamp) {
long now = System.currentTimeMillis();
time.set((now - startTime) / 1000.0);
}
};
Button startStop = new Button();
startStop.textProperty().bind(
Bindings.when(running)
.then("Stop")
.otherwise("Start"));
startStop.setOnAction(e -> {
if (running.get()) {
timer.stop();
} else {
timer.start();
}
});
VBox root = new VBox(10, label, startStop);
root.setAlignment(Pos.CENTER);
Scene scene = new Scene(root, 320, 120);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
The value passed into the handle(...) method is actually a timestamp in nanoseconds. You could use this and set the startTime to System.nanoTime(), though the precision you get there is way more than you can realistically use when the frames render at maximum of 60 frames per second.
I make this code
Timer.schedule(new Task(){
#Override
public void run() {
flyAway();
}
}, 12);
and when my game paused i do this in draw method (Actor)
Timer.instance().stop();
On resume game i do this:
Timer.instance().start();
It work, but when i pause on 2 sec and resume after 35 sec, function flyAway() executed immediately, not counting the remaining 10 seconds.
How i can paused delaySeconds. Thanks.
Sorry for my bad English.
FlyAvay code
//Муха улетает
public void flyAway(){
flyAway = 1;
targetFlyX = Gdx.graphics.getWidth() / 2;
targetFlyY = Gdx.graphics.getHeight() + 300;
this.setColor(1f, 1f, 1f, 0.2f);
this.addAction(sequence(moveTo(targetFlyX, targetFlyY, flySpeed), desctroyFlyAction));
}
Action desctroyFlyAction = new Action(){
public boolean act( float delta ) {
removeFly();
return true;
}
};
public void removeFly(){
rectBounds.set(1, 1, 0, 0);
this.remove();
}
I had this problem and I know this is late but maybe it'll help someone else. The problem is that when a Timer is created, it is given a time on the system's clock to execute rather than a specified amount of time until it executes. So when you call Timer.stop in your pause method, wait 35 seconds, then call Timer.start in the resume method, the time that task was supposed to be completed had already passed so it was executed right away.
My solution was just to save the current system's time on pause, then subtract it from the current time on resume, and add the difference as a delay to the Timer using Timer.delay() before calling Timer.start() again.
public void pause() {
timerDelay = TimeUtils.nanosToMillis(TimeUtils.nanoTime());
Timer.stop();
}
public void resume() {
Timer.delay(TimeUtils.nanosToMillis(TimeUtils.nanoTime()) - timerDelay);
Timer.start();
}
I have start writing a shooting game JavaFX application. I am using Shape.intersect() to check the collision of bullet and the target. Below is my code and I made it simple so as to post here.
public class TestShapeIntersect extends Application{
AnchorPane anchorPane;
ArrayList<Rectangle> targetObjects;
public static void main(String[] arg){
launch(arg);
}
#Override
public void start(Stage stage) throws Exception {
final Rectangle gun = new Rectangle(50, 50, Color.RED);
anchorPane = new AnchorPane();
anchorPane.getChildren().add(gun);
generateTargetObjects(50); // Number of target objects
anchorPane.getChildren().addAll(targetObjects);
gun.setX(50);
gun.setY(200);
Scene scene = new Scene(anchorPane,300,300,Color.GREEN);
stage.setScene(scene);
stage.show();
scene.setOnKeyPressed(new EventHandler<KeyEvent>() {
#Override
public void handle(KeyEvent event) {
Rectangle bullet = new Rectangle(5,10,Color.ORANGE);
bullet.setX(75);
bullet.setY(200);
anchorPane.getChildren().add(bullet);
animateBullet(bullet);
}
});
}
private void generateTargetObjects(int noOfTargetObj) {
targetObjects = new ArrayList<Rectangle>();
for(int i=1; i<=noOfTargetObj;i++){
Rectangle rect = new Rectangle(30, 30, Color.YELLOW);
targetObjects.add(rect);
}
}
void animateBullet(final Rectangle bullet){
Timeline timeline = new Timeline();
timeline.setCycleCount(500);
final KeyFrame kf = new KeyFrame(Duration.millis(2), new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
bullet.setY(bullet.getY()-1);
checkCollision(bullet);
}
});
timeline.getKeyFrames().add(kf);
timeline.play();
}
//This method will check if there is any collision happened between the bullets and the targets.
//If collision happens then both bullet and target object will be disappeared.
void checkCollision(Rectangle bullet){
int noOfTargetObjs = targetObjects.size();
for(int i=0; i<noOfTargetObjs;i++)
{
if(targetObjects.get(i).isVisible()==true && bullet.isVisible()==true){
Shape intersectShape= Shape.intersect(bullet, targetObjects.get(i));
if(intersectShape.getBoundsInLocal().getWidth() != -1){
targetObjects.get(i).setVisible(false);
bullet.setVisible(false);
}
}
}
}
}
I have not yet aligned the nodes properly.Here the 'gun' rectangle will fire 'bullet' rectangle whenever any key press event is detected.
The problem is for every every first bullet fired in each application session, the very first bullet is not animated properly (means the bullet is not going in it path continuously). But after the first bullet has gone the remaining bullets are animated properly. This performance issue increases with the number of 'target' objects increases.
I have found out that the issue is because of this line:
Shape intersectShape= Shape.intersect(bullet, targetObjects.get(i));
Could anyone let me know why this happens and what could be the solution to resolve this issue? Or is it because of the way that I'm implementing?
I experienced a different behaviour when i executed your application. My first shot was moving fine without any interruptions in the translation. But after several shots the application began to slow down. I tried to improve the performance of your code by doing the following steps:
void animateBullet(final Rectangle bullet){
final Timeline timeline = new Timeline();
timeline.setCycleCount(125); //changed
final KeyFrame kf = new KeyFrame(Duration.millis(16), new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
bullet.setY(bullet.getY()-8); //changed
checkCollisionThreaded(bullet); //changed
//added
if(bullet.getX() < 0 || bullet.getX() > bullet.getParent().getBoundsInParent().getWidth()
|| bullet.getY() < 0 || bullet.getY() > bullet.getParent().getBoundsInParent().getHeight())
{
bullet.setVisible(false);
timeline.stop();
AnchorPane ap = (AnchorPane) bullet.getParent();
ap.getChildren().remove(bullet);
}
}
});
timeline.getKeyFrames().add(kf);
timeline.play();
}
Your value for the Duration.millis factor in the KeyFrame was 2, which is not really necessary to run a fluent animation, because JavaFX has a fixed framerate of 60 frame per second, which means that every 16,7 milliseconds a new frame is rendered and displayed. So you can use 16ms as frame-duration without making the animation stutter.
The if-statement checks if the bullet is outside the visible screen, which could happen in your previous code. Non-visible nodes should be removed from the scene graph. It doesn't help if you set a node to setVisible(false), because the node will stay on the scene graph. The Timeline animation should also be stopped, because it would trigger new checkCollision calls. As you can see, i changed the method checkCollision to checkCollisionThreaded. The method is shown below.
public void checkCollisionThreaded(final Rectangle bullet)
{
final int noOfTargetObjs = targetObjects.size();
Task<Integer> t = new Task<Integer>()
{
#Override
protected Integer call() throws Exception
{
for(int i=0; i<noOfTargetObjs;i++)
{
if(targetObjects.get(i).isVisible()==true && bullet.isVisible()==true){
Shape intersectShape= Shape.intersect(bullet, targetObjects.get(i));
if(intersectShape.getBoundsInLocal().getWidth() != -1){
return i;
}
}
}
return -1;
}
#Override
protected void succeeded()
{
super.succeeded();
if(this.getValue().intValue() != -1)
{
Node obj = targetObjects.get(this.getValue().intValue());
obj.setVisible(false);
AnchorPane ap = (AnchorPane) obj.getParent();
ap.getChildren().remove(obj);
targetObjects.remove(this.getValue().intValue());
bullet.setVisible(false);
}
}
};
Thread thread = new Thread(t);
thread.start();
}
There are some violations against the rule "don't touch any objects on the scene graph with a thread different to the JavaFX application thread", but as far as i can see, only reading methods access the scene graph (and it's objects) in the call() method. This method is run on a new Thread, which improves performance. The method succeeded() is run on the JavaFX Application Thread, so that we can safely remove things from our scene graph. I assumed that you want to remove your targets from the scene once they were hit.
It should be said that there might be issues related to the multithreaded code. There could be errors when getting final int noOfTargetObjs = targetObjects.size(); while modifying it on another thread. I left out any synchronization to reduce the complexity of the code.
My guess is that you're sending way too many requests to Shape.intersect(...), which is probably a fairly expensive method to execute. Initially this is causing performance problems, but when the number of calls to the method hits some threshold, the JVM's JIT compiler kicks in and compiles that method, relieving some of the problems. (Again, this is all guesswork.)
Using a TranslateTransition for the bullet and listening to its boundsInParent property to check for collisions seems to work better. I think the reason is that using this technique only checks for collisions when the JavaFX machinery actually moves the bullet. In your code you are performing these checks much more often.
Here's an example.