Display elapsed time in screen as Score java libgdx - java

I'm new in game programming and I'm building one with libgdx in Android Studio.
I want the score to be the same as the elapsed time in the PlayState.
How can I place it in the top right corner with the "score" label next to it?
Please help! I'm stucked!
This is a little bit of code I've made in the PlayState.
public class PlayState extends State {
private Texture bg;
private Texture ground;
long startTime;
public static BitmapFont font;
private int score;
private String scoreText;
public PlayState(GameStateManager gsm) {
super(gsm);
bg = new Texture("bg.png");
ground = new Texture("ground.png");
startTime = System.currentTimeMillis();
font = new BitmapFont(Gdx.files.internal("font.fnt"));
font.getData().setScale(.25f, .25f);
}
#Override
protected void handleInput() {
}
#Override
public void update(float dt) {
handleInput();
cam.update();
}
#Override
public void render(SpriteBatch sb) {
sb.setProjectionMatrix(cam.combined);
sb.begin();
sb.draw(bg, cam.position.x - (cam.viewportWidth / 2), 0);
System.out.println("Score = " + ((System.currentTimeMillis() - startTime) / 100));
sb.end();
}
#Override
public void dispose() {
bg.dispose();
ground.dispose();
System.out.println("Play State Disposed");
}
}

In your xml, place a TextView in the top right corner. You can use a Handler so that you can update the time and UI concurrently. Here is something I am using now..
Your XML would look something like this
<TextView
android:id="#+id/txt_Timer"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_marginRight="5dp" />
I put that in my FrameLayout
In your activity you can launch the Timer by doing this...
//Define global variable for time elapse and TextView
private int timeElapsed = 0;
private TextView txt_Timer;
//In the onCreate method define
txt_Timer = (TextView) findViewById(R.id.txt_View);
txt_Timer.setText(Integer.toString(timeElapsed));
//Place this wheree
Handler handler = new Handler();
Runnable r = new Runnable() {
public void run() {
//Update and display
timeElapsed += 1;
txt_Timer.setText(Integer.toString(timeElapsed));
//Call this again in one second (1000 milliseconds)
handler.postDelayed(this, 1000);
}
};
You may want to declare the Runnable globally so you can access elsewhere in the class. And when you want to start the timer you can call....
handler.postDelayed(r, 1000);
If you want to pause the timer you can call....
handler.removeCallbacks(r);
I don't know if this will solve your problem but it should working, assuming you are in an activity. I never made a mobile game with libgdx...

Related

Change Color everysecond AndroidStudio

Hello again guys firstly i am new at android studio.i am trying to make countdowntimer and i made it its working
then i want to make change background color every tick , every second.
thank you!
new CountDownTimer(3000, 1000) {
#Override
public void onTick(long millisUntilFinished) {
getWindow().getDecorView().setBackgroundColor(Color.WHITE);
String text = String.format(Locale.getDefault(), " %02d:%02d",
TimeUnit.MILLISECONDS.toMinutes(millisUntilFinished) % 60,
TimeUnit.MILLISECONDS.toSeconds(millisUntilFinished) % 60);
textView.setText(text);
getWindow().getDecorView().setBackgroundColor(Color.RED);
}
#Override
public void onFinish() {
textView.setTextSize(20);
textView.setText("done.");
getWindow().getDecorView().setBackgroundColor(Color.rgb(0, 153, 51));
}
}.start();
The problem lies in the fact that on each tick you are setting the background color to white and then immediately to red, inside onTick. This will most likely result for the user to not be able to see the white color, because it is immediately overwritten on every tick. You need a way to change to a single color for each tick.
Don't forget to always run UI related code on the UI thread. You can read more about on how to do it here.
In case you want to alternate between white color and red for every tick, then you can have a flag in your CountDownTimer like so:
new CountDownTimer(10000, 1000) {
private boolean white = true;
#Override
public void onTick(final long millisUntilFinished) {
runOnUiThread(new Runnable() {
#Override
public void run() {
final int color;
if (white) //Select color depending on flag's value:
color = Color.WHITE;
else
color = Color.RED;
white = !white; //Flip the flag.
getWindow().getDecorView().setBackgroundColor(color);
String text = String.format(Locale.getDefault(), " %02d:%02d",
TimeUnit.MILLISECONDS.toMinutes(millisUntilFinished) % 60,
TimeUnit.MILLISECONDS.toSeconds(millisUntilFinished) % 60);
textView.setText(text);
}
});
}
#Override
public void onFinish() {
runOnUiThread(new Runnable() {
#Override
public void run() {
textView.setTextSize(20);
textView.setText("done.");
getWindow().getDecorView().setBackgroundColor(Color.rgb(0, 153, 51));
}
});
}
}.start();
Or better, you can rely on the value millisUntilFinished which is given as the single argument to onTick, like so:
final long total = 10000, interval = 1000;
new CountDownTimer(total, interval) {
#Override
public void onTick(final long millisUntilFinished) {
runOnUiThread(new Runnable() {
#Override
public void run() {
final long millisElapsed = total - millisUntilFinished;
final int color;
if ((millisElapsed / interval) % 2 == 0)
color = Color.WHITE;
else
color = Color.RED;
getWindow().getDecorView().setBackgroundColor(color);
String text = String.format(Locale.getDefault(), " %02d:%02d",
TimeUnit.MILLISECONDS.toMinutes(millisUntilFinished) % 60,
TimeUnit.MILLISECONDS.toSeconds(millisUntilFinished) % 60);
textView.setText(text);
}
});
}
#Override
public void onFinish() {
runOnUiThread(new Runnable() {
#Override
public void run() {
textView.setTextSize(20);
textView.setText("done.");
getWindow().getDecorView().setBackgroundColor(Color.rgb(0, 153, 51));
}
});
}
}.start();
This approach should be better because if I understand correctly from the documentation of CountDownTimer, it seems that the millisUntilFinished (given in onTick) is not guaranteed to be a multiple of the interval, which would depend also on the amount of work the onTick method is doing.

how to keep the graphic2D shape to stay for a period time in java?

I'm going to create a moving circle for my later project, and the circle will keep moving, and it interior color will change like color emitting , the changing color will from little circle to larger circle in 5 levels, so how to keep each color change to stay a while and I hope these code present with thread, so I create two thread for the purpose, one control circle moving, another control the circle's interior color emit
here is my code:
import java.awt.*;
import static java.awt.Color.black;
import static java.awt.Color.yellow;
import static java.awt.FlowLayout.RIGHT;
import java.awt.event.*;
import java.awt.geom.Arc2D;
import java.awt.geom.Ellipse2D;
import java.awt.geom.GeneralPath;
import java.awt.geom.Rectangle2D;
import static java.lang.Math.abs;
import java.util.Random;
import javax.swing.*;
import java.util.concurrent.ExecutorService;
class thepane extends JPanel implements Runnable{
public float x,y,r;
public float speedx,speedy;
thepane(float lx,float ly,float lr, float sx,float sy){
loadspeed(sx,sy);
load(lx,ly,lr);
for(int i=0;i<5;i++)
fc[i]=new Color(nd.nextInt(255),nd.nextInt(255),nd.nextInt(255));
}
public void load(float lx,float ly,float lr){
x=lx;y=ly;r=lr;
}
public void loadspeed(float sx,float sy){
speedx=sx;speedy=sy;
}
public void xmoving(){
x+=speedx;
}
public void ymoving(){
y-=speedy;
}
public void touchbond(){
if(x>getWidth()-r||x<0)
speedx*=-1;
if(y>getHeight()-r||y<0)
speedy*=-1;
if(x>getWidth()-r)
x=getWidth()-r;
else if(x<0)
x=0;
if(y>getHeight()-r)
y=getHeight()-r;
else if(y<0)
y=0;
}
Random nd=new Random();
int colorcount=0;
int emitcount=0;
boolean emit=false;
Color[] fc=new Color[5];
Graphics2D comp2D ;
Thread athread;
#Override
public void paintComponent(Graphics comp) {
comp2D = (Graphics2D) comp;
//create rectangle background
comp2D.setColor(Color.BLACK);
comp2D.fillRect(0, 0, getWidth(), getHeight());
//set information text
comp2D.setFont( new Font("Arial", Font.BOLD, 12));
comp2D.setColor(Color.WHITE);
comp2D.drawString("Centre("+(x+r/2)+' '+(y+r/2)+"), xspeed: "+speedx+" yspeed: "+speedy, 10f,10f );
comp2D.drawString("panel width "+getWidth()+" panel height "+getHeight()+" circle radius "
+r, 10f, 22f);
}
//thread run()
#Override
public void run() {
x=100;y=100;
System.out.println("thread in pane start!!!! (current colorcount = "+colorcount+')');
while(true){
circleEmit(fc[colorcount%5]);
repaint();
sleeping(1);
// comp2D=(Graphics2D)this.getGraphics();
// colorEmit(comp2D);
}
}
//wait method
public void waiting(){
try{wait();}
catch(Exception e){}}
public void waiting2D(int time){
try{comp2D.wait(time);}
catch(Exception e){}
}
public void waiting(int time){
try{wait(time);}
catch(Exception e){}
}
//sleep method
public void sleeping(int n){
try{
Thread.sleep(n);
}catch(Exception f){
System.out.print(f);
}
}
Ellipse2D.Float[] e=new Ellipse2D.Float[5];
public void loade(){
float centrex=x+r/2,centrey=y+r/2;
e[0]= new Ellipse2D.Float(centrex-r/10, centrey-r/10, r/5, r/5);
e[1]= new Ellipse2D.Float(centrex-r/5, centrey-r/5, 2*r/5, 2*r/5);
e[2]= new Ellipse2D.Float(centrex-3*r/10, centrey-3*r/10, 3*r/5, 3*r/5);
e[3]= new Ellipse2D.Float(centrex-2*r/5, centrey-2*r/5, 4*r/5, 4*r/5);
e[4]= new Ellipse2D.Float(centrex-r/2, centrey-r/2, r, r);
}
public Color ff;
public synchronized void circleEmit(Color fc){
comp2D=(Graphics2D)this.getGraphics();
loade();
comp2D.setColor(fc);
comp2D.fill(e[emitcount%5]);
waiting(5);
emitcount++;
}
public synchronized void callnotify(){
this.notify();
}
//iterative way to generate color emit
public void colorEmit(Graphics2D comp2D){
//create circle
//set circle property
float centrex=x+r/2,centrey=y+r/2;//so x=centrex-r/2;y=centrey+r/2
Ellipse2D.Float e1 = new Ellipse2D.Float(centrex-r/10, centrey-r/10, r/5, r/5);
Ellipse2D.Float e2 = new Ellipse2D.Float(centrex-r/5, centrey-r/5, 2*r/5, 2*r/5);
Ellipse2D.Float e3 = new Ellipse2D.Float(centrex-3*r/10, centrey-3*r/10, 3*r/5, 3*r/5);
Ellipse2D.Float e4 = new Ellipse2D.Float(centrex-2*r/5, centrey-2*r/5, 4*r/5, 4*r/5);
Ellipse2D.Float e5 = new Ellipse2D.Float(centrex-r/2, centrey-r/2, r, r);
if(colorcount>=4)
emit(comp2D,fc[(colorcount-4)%5],e5);
waiting(1000);
if(colorcount>=3)
emit(comp2D,fc[(colorcount-3)%5],e4);
waiting(1000);
if(colorcount>=2)
emit(comp2D,fc[(colorcount-2)%5],e3);
waiting(1000);
if(colorcount>=1)
emit(comp2D,fc[(colorcount-1)%5],e2);
waiting(1000);
emit(comp2D,fc[colorcount%5],e1);
waiting(1000);
colorcount++;
}
private void emit(Graphics2D comp,Color thecolor,Ellipse2D.Float f){
comp.setColor(thecolor);
comp.fill(f);
}
}
//------------------------------------------------------------------------------------
//main class
public class drawpanel extends Thread implements ActionListener{
JFrame frame=new JFrame();
thepane panel;
JButton FlyingBalls=new JButton("balls"),exit=new JButton("Exit"),stop=new JButton("Stop");
JButton slow=new JButton("slow down"),resume=new JButton("resume");
Float x,y,r;
public void sleeping(int n){
try{
Thread.sleep(n);
}catch(Exception f){
System.out.print(f);
}
}
Thread newthread,pthread;
Thread[] five=new Thread[5];
drawpanel(){
frame.setTitle("FlyingBalls");
frame.setLocation(100, 100);
frame.setLayout(null);
//x,y,r,speedx,speedy
panel=new thepane(nd.nextInt(800),nd.nextInt(500),40,nd.nextFloat()*20+1,nd.nextFloat()*10+1);
panel.setSize(800,500);
frame.setSize(810,580);
frame.add(panel);
FlyingBalls.setSize(80,30);exit.setSize(70,30);stop.setSize(70,30);slow.setSize(140,30);
resume.setSize(100,30);
FlyingBalls.addActionListener(this);
exit.addActionListener(this);
stop.addActionListener(this);slow.addActionListener(this);resume.addActionListener(this);
frame.add(FlyingBalls);frame.add(exit); frame.add(stop);frame.add(slow);frame.add(resume);
FlyingBalls.setLocation(20,500);exit.setLocation(190, 500);stop.setLocation(110,500);
slow.setLocation(270,500);resume.setLocation(420,500);
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
//control moving ball
newthread=new Thread(this);
//control color change
for(int i=0;i<5;i++){
five[i]=new Thread(panel);
}
// newthread.start();
panel.colorcount++;
five[0].start();
panel.colorcount=2;
// five[1].start();
panel.waiting(5);
}
public static void main(String[] arg){
drawpanel apanel=new drawpanel();
}
int bw=800,bh=500;
void setp(){
x=panel.x;y=panel.y;
}
void touchbond(){
System.out.println("width:"+panel.getWidth()+"Height:"+panel.getHeight());
System.out.println("xposition:"+x+"yposition:"+y);
if(x+r>panel.getWidth()){
panel.speedx*=-1;
x=bw-r;
}
else if(x-r<0){
panel.speedx*=-1;
x=r;
}
if(y-r<0){
panel.speedy*=-1;
y=r;
}
else if(y+r>panel.getHeight()){
panel.speedy*=-1;
y=bh-r;
}
panel.x=x;panel.y=y;
}
int T=10;
Random nd=new Random();
#Override
public void run(){
r=panel.r;
panel.loadspeed(-6.33f,-3.4f);
while(true){
if(stopcount==0){//button control variable
panel.xmoving();panel.ymoving();
panel.touchbond();
sleeping(T);}
panel.loade();
// panel.callnotify();
// panel.colorEmit(panel.comp2D);
panel.repaint();
}
}
#Override
public void start(){
}
int count=0,stopcount=0;
#Override
public void actionPerformed(ActionEvent e) {
if(e.getSource()==exit){
System.exit(0);
}
if(e.getSource()==FlyingBalls){
//panel=new thepane();
}
if(e.getSource()==resume){
stopcount=0;T=10;
panel.emit=false;
}
if(e.getSource()==slow){
if(count%2==0)
T=500;
else
T=10;
count++;
}
if(e.getSource()==stop){
stopcount++;
panel.emit=true;
}
}
}
So, lots of theory to cover.
Firstly...
Animation is not easy, good animation is hard.
Swing is single threaded and is not thread safe
This means that you should not perform any long running or blocking operations within the context of the Event Dispatching Thread.
It also means that you shouldn't modify the UI or anything the UI relies on from outside the context of the Event Dispatching Thread
More threads != more work
More threads doesn't always mean you're going to get more done. In fact, in this scenario, it could really cause a huge number of issues, as you need the ability to reason out the state at a single point in time (when painting)
Animation Theory
Okay, animation is simply the illusion of change, how you accomplish that will come down to the problem you trying to solve.
For me, the best animations are time based animations, not linear.
A linear animation keeps updating from its start state till it reaches its end state, in a constant progression. These don't tend to scale well and can suffer issues on low performant systems.
A time based animation is one where the amount of time is defined and then, based on a anchor time (ie start time) and the state of the animation is updated based on the amount of time which is passed. This is a really simple way to achieve "frame dropping". You'd also be very surprised to find that in general terms, time based animations tend to look better across more platforms.
A time based animation is also more capable of generating "easement" effects, but that's getting way deeper then we need to go right now.
Okay, but what's this got to do with your problem? Well, actually, quite a bit.
The first thing we need is some kind of "main-loop" from which all the animation can be driven. Typically, I'd look to a good animation library, but failing that, a simple Swing Timer will do the basic good really well.
It generates its ticks in the Event Dispatching Thread, which makes it very useful for our needs. See How to Use Swing Timers for more details
So, we start with something like...
private Timer timer;
//...
timer = new Timer(5, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
// Update the state
repaint();
}
});
//...
timer.start();
This gives us our "main loop", from which we can update the state as needed and then trigger a repaint of the component.
For the purpose of this demonstration, I'm going to devise a self-contained unit of "animation duration", used to track the amount of time which has passed since the animation was started, this is personal choice, but it would allow me to drive a number of animations and it contains the logic to a single unit of work.
public class AnimationDuration {
private Duration duration;
private Instant startedAt;
public AnimationDuration(Duration duration) {
this.duration = duration;
}
public Duration getDuration() {
return duration;
}
public void start() {
startedAt = Instant.now();
}
public void stop() {
startedAt = null;
}
public boolean isRunning() {
return startedAt != null;
}
public float getProgress() {
Duration runningTime = Duration.between(startedAt, Instant.now());
if (runningTime.compareTo(duration) > 0) {
runningTime = duration;
}
long total = duration.toMillis();
float progress = runningTime.toMillis() / (float) total;
return progress;
}
}
This basically allows to trigger the animation to start running (anchor point in time) and then get the progress of the animation at any point in time. This provides a normalised concept from 0-1, so if we want to make it longer or shorter, all we do is adjust the duration and everything else is taken care of.
For your specific problem, I'd consider some kind of "time line" or "key frames", which defines that certain actions should occur at certain points of time along the time line.
Now, the following is a really simple concept, but it gets the job.
public interface KeyFrame {
public float getProgress();
}
public class TimeLine<K extends KeyFrame> {
private List<K> keyFrames;
public TimeLine() {
keyFrames = new ArrayList<>(25);
}
// Returns the key frames between the current progression
public K getKeyFrameAt(float progress) {
for (int index = 0; index < keyFrames.size(); index++) {
K keyFrame = keyFrames.get(index);
if (progress >= keyFrame.getProgress()) {
if (index + 1 < keyFrames.size()) {
K nextFrame = keyFrames.get(index + 1);
// But only if your between each other
if (progress < nextFrame.getProgress()) {
return keyFrame;
}
} else {
// Nothing after me :D
return keyFrame;
}
}
}
return null;
}
public void add(K keyFrame) {
keyFrames.add(keyFrame);
Collections.sort(keyFrames, new Comparator<KeyFrame>() {
#Override
public int compare(KeyFrame lhs, KeyFrame rhs) {
if (lhs.getProgress() > rhs.getProgress()) {
return 1;
} else if (lhs.getProgress() < rhs.getProgress()) {
return -1;
}
return 0;
}
});
}
}
This allows you to define certain KeyFrames along the timeline, based on a normalised concept of time and then provides the ability to get the KeyFrame based on the current progression through animation.
There are much more complex solutions you might consider, which would generate self contained events based on time progressions automatically, but I prefer been able to driver the animation itself independently, makes these types of things more flexible - add a JSlider and you can manipulate the progression manually ;)
The next thing we need is something to carry the properties for the circle KeyFrame ...
public class CirclePropertiesKeyFrame implements KeyFrame {
private float progress;
private double radius;
private Color color;
public CirclePropertiesKeyFrame(float progress, double radius, Color color) {
this.progress = progress;
this.radius = radius;
this.color = color;
}
#Override
public float getProgress() {
return progress;
}
public Color getColor() {
return color;
}
public double getRadius() {
return radius;
}
#Override
public String toString() {
return "KeyFrame progress = " + getProgress() + "; raidus= " + radius + "; color = " + color;
}
}
Now, we need to put it together...
public class TestPane extends JPanel {
private AnimationDuration timelineDuration;
private TimeLine<CirclePropertiesKeyFrame> timeLine;
private Timer timer;
private CirclePropertiesKeyFrame circleProperties;
public TestPane() {
timelineDuration = new AnimationDuration(Duration.ofSeconds(10));
timeLine = new TimeLine<>();
timeLine.add(new CirclePropertiesKeyFrame(0, 5, Color.CYAN));
timeLine.add(new CirclePropertiesKeyFrame(0.2f, 10, Color.BLUE));
timeLine.add(new CirclePropertiesKeyFrame(0.4f, 15, Color.GREEN));
timeLine.add(new CirclePropertiesKeyFrame(0.6f, 20, Color.YELLOW));
timeLine.add(new CirclePropertiesKeyFrame(0.8f, 25, Color.MAGENTA));
timer = new Timer(5, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
if (timelineDuration.isRunning()) {
float progress = timelineDuration.getProgress();
if (progress >= 1.0) {
timelineDuration.stop();
}
CirclePropertiesKeyFrame keyFrame = timeLine.getKeyFrameAt(progress);
circleProperties = keyFrame;
}
repaint();
}
});
}
#Override
public void addNotify() {
super.addNotify();
timelineDuration.start();
timer.start();
}
#Override
public void removeNotify() {
super.removeNotify();
timer.stop();
timelineDuration.stop();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
if (circleProperties != null) {
double radius = circleProperties.radius;
double xPos = (getWidth() / 2) - radius;
double yPos = (getHeight() / 2) - radius;
g2d.setColor(circleProperties.color);
g2d.fill(new Ellipse2D.Double(xPos, yPos, radius * 2, radius * 2));
}
g2d.dispose();
}
}
And then we end up with something like...
Now, this is a 10 second sequence, so every 2 seconds it will update. Try changing the duration of the AnimationDuration and see what happens.
Note This is a non-repeating animation (it doesn't loop). You could make it loop, but the calculation to do so becomes more complicate, as you need to consider by how much you're over the expected Duration and then apply that to the next cycle, so it looks smooth
But what about movement?
Well, actually, pretty much already answered that question. You would also place the movement code inside the Timers ActionListener, right before the repaint request. In fact, I might be tempted to create some kind of class that could take the current KeyFrame information and combine it with the location properties, this would then be used the paintComponent method to draw the circle.
I want to blend the animation states ...
Well, that's a much more difficult question, especially when it comes to colors.
The basic theory is, you need the two key frames which set either side of the current progression. You would then apply a "blending" algorithm to calculate the amount of change to be applied between the two key frames.
Not impossible, just a step more difficult

How do I get multiple Java threads to pause and resume at a user's request?

I'm creating a 20-minute countdown timer application. I'm using JavaFX SceneBuilder to do this. The timer is composed of two labels (one for minutes, one for seconds--each composed of a CountdownTimer class object), and a progress bar (the timer looks like this). Each of these components are separate and running on separate threads concurrently to prevent the UI from freezing up. And it works.
The problem:
The three threads (minutesThread, secondsThread, progressBarUpdaterThread) I need to be able to pause and resume are regular .java classes. When the user clicks the play (start) button, the click signals the FXMLDocumentController (the class that controls how the components in the UI are updated) method startTimer() to do work regarding the timer.
Right now the only functionality startTimer() in FXMLDocumentController has is: user clicks play (start) button --> timer begins counting down.
I want the user to be able to pause and resume the timer with this same button. I've tried using synchronization across the FXMLDocumentController class and the other three threads to no avail in multiple different ways (admittedly, I have almost no experience coding for concurrency). I just want to be able to pause and play the timer!
Can anyone offer me advice in how to go about this? Thanks in advance.
startTimer() in FXMLDocumentController.java (used to start the countdown timer):
#FXML
void startTimer(MouseEvent event) throws FileNotFoundException {
// update click count so user can switch between pause and start
startTimerButtonClickCount++;
// create a pause button image to replace the start button image when the user pauses the timer
Image pauseTimerButtonImage = new Image(new
FileInputStream("/Users/Home/NetBeansProjects/Take20/src/Images/pause2_black_18dp.png"));
// setting imageview to be used when user clicks on start button to pause it
ImageView pauseTimerButtonImageView = new ImageView(pauseTimerButtonImage);
// setting the width and height of the pause image
pauseTimerButtonImageView.setFitHeight(31);
pauseTimerButtonImageView.setFitWidth(28);
// preserving the pause image ratio after resize
pauseTimerButtonImageView.setPreserveRatio(true);
// create a start button image to replace the pause button image when the user unpauses the timer
Image startTimerButtonImage = new Image(new
FileInputStream("/Users/Home/NetBeansProjects/
Take20/src/Images/play_arrow2_black_18dp.png"));
ImageView startTimerButtonImageView = new ImageView(startTimerButtonImage);
startTimerButtonImageView.setFitHeight(31);
startTimerButtonImageView.setFitWidth(28);
startTimerButtonImageView.setPreserveRatio(true);
// progressBar updater
ProgressBarUpdater progressBarUpdater = new ProgressBarUpdater();
TimerThread progressBarThread = new TimerThread(progressBarUpdater);
// minutes timer
CountdownTimer minutesTimer = new CountdownTimer(19);
TimerThread minutesThread = new TimerThread(minutesTimer);
// seconds timer
CountdownTimer secondsTimer = new CountdownTimer(59);
TimerThread secondsThread = new TimerThread(secondsTimer);
// bind our components in order to update them
progressBar.progressProperty().bind(progressBarUpdater.progressProperty());
minutesTimerLabel.textProperty().bind(minutesTimer.messageProperty());
secondsTimerLabel.textProperty().bind(secondsTimer.messageProperty());
// start the threads in order to have them run parallel when the start button is clicked
progressBarThread.start();
minutesThread.start();
secondsThread.start();
// if the start button was clicked, then we set its graphic to the pause image
// if the button click count is divisible by 2, we pause it, otherwise, we play it (and change
// the button images accordingly).
if (startTimerButtonClickCount % 2 == 0) {
startTimerButton.setGraphic(pauseTimerButtonImageView);
progressBarThread.pauseThread();
minutesThread.pauseThread();
secondsThread.pauseThread();
progressBarThread.run();
minutesThread.run();
secondsThread.run();
} else {
startTimerButton.setGraphic(startTimerButtonImageView);
progressBarThread.resumeThread();
minutesThread.resumeThread();
secondsThread.resumeThread();
progressBarThread.run();
minutesThread.run();
secondsThread.run();
}
}
TimerThread (used to suspend/resume timer threads when user clicks the play/pause button in the UI):
public class TimerThread extends Thread implements Runnable {
public boolean paused = false;
public final Task<Integer> timerObject;
public final Thread thread;
public TimerThread(Task timerObject) {
this.timerObject = timerObject;
this.thread = new Thread(timerObject);
}
#Override
public void start() {
this.thread.start();
System.out.println("TimerThread started");
}
#Override
public void run() {
System.out.println("TimerThread class run() called");
try {
synchronized (this.thread) {
System.out.println("synchronized called");
while (paused) {
System.out.println("wait called");
this.thread.wait();
System.out.println("waiting...");
}
}
} catch (Exception e) {
System.out.println("exception caught in TimerThread");
}
}
synchronized void pauseThread() {
paused = true;
}
synchronized void resumeThread() {
paused = false;
notify();
}
}
CountdownTimer.java (used to create and update the minutes and seconds of the countdown timer):
public class CountdownTimer extends Task<Integer> {
private int time;
private Timer timer;
private int timerDelay;
private int timerPeriod;
private int repetitions;
public CountdownTimer(int time) {
this.time = time;
this.timer = new Timer();
this.repetitions = 1;
}
#Override
protected Integer call() throws Exception {
// we will create a new thread for each time unit (minutes, seconds)
// we start with whatever time is passed to the constructor
// we have threads devoted to each case so both minutes and second cases can run parallel to each other.
switch (time) {
// for our minutes timer
case 19:
// first display should be 19 first since our starting timer time should be 19:59
updateMessage("19");
// set delay and period to change every minute of the countdown
// 60,000 milliseconds in one minute
timerDelay = 60000;
timerPeriod = 60000;
System.out.println("Running minutesthread....");
// use a timertask to loop through time at a fixed rate as set by timerDelay, until the timer reaches 0 and is cancelled
timer.scheduleAtFixedRate(new TimerTask() {
#Override
public void run() {
//check if the flag is divisible by 2, then we sleep this thread
// if time reaches 0, we want to update the minute label to 00
if (time == 0) {
updateMessage("0" + Integer.toString(time));
timer.cancel();
timer.purge();
// if the time is a single digit, append a 0 and reduce time by 1
} else if (time <= 10) {
--time;
updateMessage("0" + Integer.toString(time));
// otherwise, we we default to reducing time by 1, every minute
} else {
--time;
updateMessage(Integer.toString(time));
}
}
}, timerDelay, timerPeriod);
// exit switch statement once we finish our work
break;
// for our seconds timer
case 59:
// first display 59 first since our starting timer time should be 19:59
updateMessage("59");
// use a counter to count repetitions so we can cancel the timer when it arrives at 0, after 20 repetitions
// set delay and period to change every second of the countdown
// 1000 milliseconds in one second
timerDelay = 1000;
timerPeriod = 1000;
System.out.println("Running seconds thread....");
// use a timertask to loop through time at a fixed rate as set by timerDelay, until the timer reaches 0 and is cancelled
timer.scheduleAtFixedRate(new TimerTask() {
#Override
public void run() {
--time;
System.out.println("repititions: " + repetitions);
// Use a counter to count repetitions so we can cancel the timer when it arrives at 0, after 1200 repetitions
// We will reach 1200 repetitions at the same time as the time variable reaches 0, since the timer
// loops/counts down every second (1000ms).
// 1200 seconds = 20 minutes * 60 seconds (1 minute)
repetitions++;
if (time == 0) {
if (repetitions == 1200) {
// reset repetitions if user decides to click play again
repetitions = 0;
timer.cancel();
System.out.println("repetitions ran");
}
updateMessage("0" + Integer.toString(time));
// reset timer to 60, so it will countdown again from 60 after reaching 0 (since we have to repeat the seconds timer multiple times,
// unlike the minutes timer, which only needs to run once
time = 60;
System.out.println("time == 00 ran");
} else if (time < 10 && time > 0) {
updateMessage("0" + Integer.toString(time));
} else {
updateMessage(Integer.toString(time));
}
}
}, timerDelay, timerPeriod);
// exit switch statement once we finish our work
break;
}
return null;
}
}
ProgressBarUpdater.java (used to update the progress bar as the countdown timer counts down):
public class ProgressBarUpdater extends Task<Integer> {
private int progressBarPeriod;
private Timer timer;
private double time;
public ProgressBarUpdater() {
this.timer = new Timer();
this.time = 1200000;
}
#Override
protected Integer call() throws Exception {
progressBarPeriod = 10;
System.out.println("Running progressBar thread....");
// using a timer task, we update our progressBar by reducing the filled progressBar every 9.68 milliseconds
// (instead of 10s to account for any delay in program runtime) to ensure that the progressBar ends at the same time our timer reaches 0.
// according to its max (1200000ms or 20 minutes)
timer.scheduleAtFixedRate(new TimerTask() {
#Override
public void run() {
time -= 9.68;
updateProgress(time, 1200000);
System.out.println("progressBarUpdater is running");
}
}, 0, progressBarPeriod);
return null;
}
#Override
protected void updateProgress(double workDone, double maxTime) {
super.updateProgress(workDone, maxTime);
}
}
As I mentioned in a comment, using a background thread for this, let alone three(!) background threads, will only make this harder to implement and reason about. It would be better to use the animation API provided by JavaFX—it's asynchronous but still executes on the JavaFX Application Thread. And as mentioned by others, you only need one value to represent the time remaining and another value representing the duration. From there you can display the minutes, seconds, and progress.
Personally, I would use an AnimationTimer as it gives you the timestamp of the current frame which you can use to calculate how much time is left. To make things easier to use I would also wrap the AnimationTimer in another class and have that latter class expose an API more appropriate for countdown timers. For example:
package com.example;
import java.util.concurrent.TimeUnit;
import javafx.animation.AnimationTimer;
import javafx.beans.property.LongProperty;
import javafx.beans.property.ReadOnlyDoubleProperty;
import javafx.beans.property.ReadOnlyDoubleWrapper;
import javafx.beans.property.ReadOnlyLongProperty;
import javafx.beans.property.ReadOnlyLongWrapper;
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.beans.property.SimpleLongProperty;
public class CountdownTimer {
private static long toMillis(long nanos) {
return TimeUnit.NANOSECONDS.toMillis(nanos);
}
/* *********************************************************************
* *
* Instance Fields *
* *
***********************************************************************/
private final Timer timer = new Timer();
private long cachedDuration;
/* *********************************************************************
* *
* Constructors *
* *
***********************************************************************/
public CountdownTimer() {}
public CountdownTimer(long duration) {
setDuration(duration);
}
/* *********************************************************************
* *
* Public API *
* *
***********************************************************************/
public void start() {
if (getStatus() == Status.READY || getStatus() == Status.PAUSED) {
timer.start();
setStatus(Status.RUNNING);
}
}
public void pause() {
if (getStatus() == Status.RUNNING) {
timer.pause();
setStatus(Status.PAUSED);
}
}
public void stopAndReset() {
timer.stopAndReset();
setStatus(Status.READY);
}
/* *********************************************************************
* *
* Properties *
* *
***********************************************************************/
private final ReadOnlyObjectWrapper<Status> status = new ReadOnlyObjectWrapper<>(this, "status", Status.READY) {
#Override protected void invalidated() {
if (get() == Status.READY) {
cachedDuration = Math.abs(getDuration());
setTimeRemaining(cachedDuration);
}
}
};
private void setStatus(Status status) { this.status.set(status); }
public final Status getStatus() { return status.get(); }
public final ReadOnlyObjectProperty<Status> statusProperty() { return status.getReadOnlyProperty(); }
private final LongProperty duration = new SimpleLongProperty(this, "duration") {
#Override protected void invalidated() {
if (getStatus() == Status.READY) {
cachedDuration = Math.abs(get());
setTimeRemaining(cachedDuration);
}
}
};
public final void setDuration(long duration) { this.duration.set(duration); }
public final long getDuration() { return duration.get(); }
public final LongProperty durationProperty() { return duration; }
private final ReadOnlyLongWrapper timeRemaining = new ReadOnlyLongWrapper(this, "timeRemaining") {
#Override protected void invalidated() {
setProgress((double) (cachedDuration - get()) / (double) cachedDuration);
}
};
private void setTimeRemaining(long timeRemaining) { this.timeRemaining.set(timeRemaining); }
public final long getTimeRemaining() { return timeRemaining.get(); }
public final ReadOnlyLongProperty timeRemainingProperty() { return timeRemaining.getReadOnlyProperty(); }
private final ReadOnlyDoubleWrapper progress = new ReadOnlyDoubleWrapper(this, "progress");
private void setProgress(double progress) { this.progress.set(progress); }
public final double getProgress() { return progress.get(); }
public final ReadOnlyDoubleProperty progressProperty() { return progress.getReadOnlyProperty(); }
/* *********************************************************************
* *
* Static Classes *
* *
***********************************************************************/
public enum Status {
READY,
RUNNING,
PAUSED,
FINISHED
}
/* *********************************************************************
* *
* Classes *
* *
***********************************************************************/
private class Timer extends AnimationTimer {
private long triggerTime = Long.MIN_VALUE;
private long pauseTime = Long.MIN_VALUE;
private boolean pausing;
#Override
public void handle(long now) {
if (pausing) {
pauseTime = toMillis(now);
pausing = false;
stop();
} else {
if (triggerTime == Long.MIN_VALUE) {
triggerTime = toMillis(now) + cachedDuration;
} else if (pauseTime != Long.MIN_VALUE) {
triggerTime += toMillis(now) - pauseTime;
pauseTime = Long.MIN_VALUE;
}
long timeRemaining = Math.max(0, triggerTime - toMillis(now));
setTimeRemaining(timeRemaining);
if (timeRemaining == 0) {
setStatus(Status.FINISHED);
stop();
}
}
}
#Override
public void start() {
pausing = false;
super.start();
}
void pause() {
if (triggerTime != Long.MIN_VALUE) {
pausing = true;
} else {
stop();
}
}
void stopAndReset() {
stop();
triggerTime = Long.MIN_VALUE;
pauseTime = Long.MIN_VALUE;
pausing = false;
}
}
}
Warning: While the AnimationTimer is running the CountdownTimer instance cannot be garbage collected.
This implementation interprets both the duration and time remaining values as milliseconds. Also, changing the duration after starting the timer has no effect until after the timer is reset (i.e. calling stopAndReset()).
Here's an example of using the above CountdownTimer in an FXML-based application. Note that the example uses distinct buttons for starting, pausing, resuming, and resetting the timer. This is different than what you described in your question but you should be able to rework things to fit your needs. Also, the example provides a way to toggle whether or not the millisecond of the current second is shown.
App.fxml:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.ProgressBar?>
<?import javafx.scene.control.ToolBar?>
<?import javafx.scene.layout.StackPane?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Separator?>
<?import javafx.scene.control.CheckBox?>
<?import com.example.CountdownTimer?>
<?import com.example.CountdownTimer.Status?>
<VBox xmlns="http://javafx.com/javafx/14.0.1" xmlns:fx="http://javafx.com/fxml/1"
fx:controller="com.example.Controller" prefHeight="300" prefWidth="500">
<fx:define>
<!-- 90,000ms == 1m 30s -->
<CountdownTimer fx:id="timer" duration="90000"/>
<CountdownTimer.Status fx:id="READY" fx:value="READY"/>
<CountdownTimer.Status fx:id="RUNNING" fx:value="RUNNING"/>
<CountdownTimer.Status fx:id="PAUSED" fx:value="PAUSED"/>
</fx:define>
<ToolBar style="-fx-font: 10pt 'Monospaced';">
<Button text="Start" disable="${timer.status != READY}" focusTraversable="false"
onAction="#handleStartOrResumeTimer"/>
<Button text="Resume" disable="${timer.status != PAUSED}" focusTraversable="false"
onAction="#handleStartOrResumeTimer"/>
<Button text="Pause" disable="${timer.status != RUNNING}" focusTraversable="false" onAction="#handlePauseTimer"/>
<Button text="Reset" disable="${timer.status == READY || timer.status == RUNNING}" focusTraversable="false"
onAction="#handleResetTimer"/>
<Separator/>
<CheckBox fx:id="showMillisBox" text="Show Millis" focusTraversable="false"/>
</ToolBar>
<ProgressBar progress="${timer.progress}" maxWidth="Infinity"/>
<StackPane VBox.vgrow="ALWAYS">
<Label fx:id="timerLabel" style="-fx-font: bold 48pt 'Monospaced';"/>
</StackPane>
</VBox>
Controller.java:
package com.example;
import java.time.Duration;
import javafx.beans.binding.Bindings;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.CheckBox;
import javafx.scene.control.Label;
import javafx.scene.paint.Color;
public class Controller {
#FXML private CountdownTimer timer;
#FXML private CheckBox showMillisBox;
#FXML private Label timerLabel;
#FXML
private void initialize() {
timerLabel
.textProperty()
.bind(
Bindings.createStringBinding(
this::formatTimeRemaining,
timer.timeRemainingProperty(),
showMillisBox.selectedProperty()));
timerLabel
.textFillProperty()
.bind(
Bindings.when(timer.statusProperty().isEqualTo(CountdownTimer.Status.FINISHED))
.then(Color.FIREBRICK)
.otherwise(Color.FORESTGREEN));
}
private String formatTimeRemaining() {
Duration d = Duration.ofMillis(timer.getTimeRemaining());
if (showMillisBox.isSelected()) {
return String.format("%02d:%02d:%03d", d.toMinutes(), d.toSecondsPart(), d.toMillisPart());
}
return String.format("%02d:%02d", d.toMinutes(), d.toSecondsPart());
}
#FXML
private void handleStartOrResumeTimer(ActionEvent event) {
event.consume();
timer.start();
}
#FXML
private void handlePauseTimer(ActionEvent event) {
event.consume();
timer.pause();
}
#FXML
private void handleResetTimer(ActionEvent event) {
event.consume();
timer.stopAndReset();
}
}
Main.java:
package com.example;
import java.io.IOException;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class Main extends Application {
#Override
public void start(Stage primaryStage) throws IOException {
Parent root = FXMLLoader.load(getClass().getResource("/com/example/App.fxml"));
primaryStage.setScene(new Scene(root));
primaryStage.setTitle("Countdown Timer Example");
primaryStage.show();
}
}

Custom Animation end trigger

I'm writing a "space invaders" style app and I needed to do the move stop repeat animation that is classic to the game. I got the processes to go one way, using the code below, however when I want to go back, there does not seem to be an easy way to do this. I think the easiest way to do it is to have something happen on the end animation event, however I cannot get the event to trigger, even when calling super.end();. I've looked around for a way to trigger the event manually but to no avail. The way I am doing this may be a little sketchy but it works well enough right now to try and make it work all the way.
public void start()
{
if(begin < end) //recursive end condition
{
int distance = interval + begin; //change the distance to be traveled
//create new animation to do part of the whole animation
ObjectAnimator anim = ObjectAnimator.ofFloat(toAnimate, property, begin,distance);
TimeInterpolator inter = new TimeInterpolator() //makes the animation move with only one frame
{
public float getInterpolation(float prog)
{
return Math.round(prog * 10) / 10;
}
};
anim.setInterpolator(inter);
anim.setDuration((long)(500));
anim.addListener(new AnimatorListener()
{
#Override
public void onAnimationStart(Animator animation){}
#Override
public void onAnimationEnd(Animator animation)
{
start(); //start the next part of the movement
}
#Override
public void onAnimationCancel(Animator animation){}
#Override
public void onAnimationRepeat(Animator animation){}
});
begin = begin + interval; //update end recursion value
anim.start(); //begin the animation
}
super.end(); //this doesn't work... rip
}
public void start()
{
if(begin < end) //recursive end condition
{
int distance = interval + begin; //change the distance to be traveled
//create new animation to do part of the whole animation
ObjectAnimator anim = ObjectAnimator.ofFloat(toAnimate, property, begin,distance);
TimeInterpolator inter = new TimeInterpolator() //makes the animation move with only one frame
{
public float getInterpolation(float prog)
{
return Math.round(prog * 10) / 10;
}
};
anim.setInterpolator(inter);
anim.setDuration((long)(500));
anim.addListener(new AnimatorListener()
{
#Override
public void onAnimationStart(Animator animation){}
#Override
public void onAnimationEnd(Animator animation)
{
begin += interval;
if(begin < end){
start(); //start the next part of the movement
}
else{
// Do something else
}
}
#Override
public void onAnimationCancel(Animator animation){}
#Override
public void onAnimationRepeat(Animator animation){}
});
anim.start(); //begin the animation
}
}

GameLoop Thread occasionally slow

For the game I'm currently writing for Android devices, I've got a class called RenderView, which is a Thread and updates and renders everything. Occasionally the class logs the message "Game thread is only updating the update method and is not rendering anything". The game is running at 30 fps on my nexus s. And I get the message a couple of times throughout the session. Could someone tell me how I could optimize the class or if I'm forgetting something or that it's totally normal?
Here's my code:
public class RenderView extends SurfaceView implements Runnable {
public final String classTAG = this.getClass().getSimpleName();
Game game;
Bitmap framebuffer;
Thread gameloop;
SurfaceHolder holder;
boolean running;
int sleepTime;
int numberOfFramesSkipped;
long beginTime;
long endTime;
long lastTime;
int differenceTime;
int framePeriod;
Canvas canvas;
int frameCount;
WSLog gameEngineLog;
public RenderView(Game game, Bitmap framebuffer) {
super(game);
this.game = game;
this.framebuffer = framebuffer;
this.holder = getHolder();
framePeriod = 1000/game.getFramesPerSecond();
lastTime = System.currentTimeMillis();
gameEngineLog = game.getGameEngineLog();
}
#Override
public void run() {
while(running == true) {
if(holder.getSurface().isValid()) {
beginTime = System.currentTimeMillis();
numberOfFramesSkipped = 0;
game.getCurrentScreen().update();
game.getCurrentScreen().render(); // Draw out everything to the current virtual screen (the bitmap)
game.getGraphics().renderFrameBuffer(); // Actually draw everything to the real screen (combine both bitmaps)
canvas = holder.lockCanvas();
if(canvas != null) { // Fix for mysterious bug ( FATAL EXCEPTION: Thread)
// The viewing area of our virtual screen on our real screen
canvas.drawBitmap(framebuffer, null, game.getWSScreen().getGameScreenextendeddst(), null);
holder.unlockCanvasAndPost(canvas);
}
else {
gameEngineLog.e(classTAG, "Surface has not been created or otherwise cannot be edited");
}
endTime = System.currentTimeMillis();;
differenceTime = (int) (endTime - beginTime);
sleepTime = (int) (framePeriod - differenceTime);
if(sleepTime > 0) {
try {
Thread.sleep(sleepTime);
} catch (InterruptedException exception) {
exception.printStackTrace();
}
}
else {
while(sleepTime < 0 && numberOfFramesSkipped < game.getMaxFrameSkippes()) {
gameEngineLog.d(classTAG, "Game thread is only updating the update method and is not rendering anything");
try {
Thread.sleep(5);
}
catch (InterruptedException exception) {
exception.printStackTrace();
}
game.getCurrentScreen().update();
sleepTime += framePeriod;
numberOfFramesSkipped++;
}
}
// Frame Per Second Count
frameCount++;
if(lastTime + 1000 < System.currentTimeMillis()) {
game.getGameEngineLog().d(classTAG, "REAL FPS: " + frameCount);
lastTime = System.currentTimeMillis();
frameCount = 0;
}
}
}
}
public void resume() {
running = true;
gameloop = new Thread(this);
gameloop.start();
}
public void pause() {
running = false;
while(running == true) {
try {
gameloop.join();
running = false;
}
catch (InterruptedException e) {
}
}
gameloop = null;
}
}
Here's the code for the Graphics class (the getGraphics() just return an graphics object):
public class Graphics {
public final String classTAG = this.getClass().getSimpleName();
Game game;
Canvas frameBuffer;
Canvas canvasGameScreenextended;
Canvas canvasGameScreen; // Used for customeScreen
Bitmap gameScreenextended;
Bitmap gameScreen;
Rect gameScreendst;
Rect gameScreenextendeddst;
WSLog gameEngineLog;
Graphics(Game game, Bitmap framebuffer, Bitmap gameScreen) {
this.game = game;
// Initialize canvases to render to
frameBuffer = new Canvas(framebuffer);
canvasGameScreen = new Canvas(gameScreen);
// Initialize images to be rendered to our composition
this.gameScreen = gameScreen;
// Set up the Log
gameEngineLog = game.getGameEngineLog();
}
public void resetCanvasGameScreenextended() {
// This method has to be called each time the screen scaling type changes
canvasGameScreenextended = new Canvas(game.getWSScreen().getGameScreenextended());
gameScreenextended = game.getWSScreen().getGameScreenextended();
}
public Canvas getCanvasGameScreenextended() {
return canvasGameScreenextended;
}
public Canvas getCanvasGameScreen() {
return canvasGameScreen;
}
public void renderFrameBuffer() {
// Composition
// First layer (bottom)
frameBuffer.drawBitmap(gameScreen, null, game.getWSScreen().getGameScreendst(), null);
// Second layer (top)
frameBuffer.drawBitmap(gameScreenextended, null, game.getWSScreen().getGameScreenextendeddst(), null);
}
public void clearFrameBuffer() {
canvasGameScreen.drawColor(Color.BLACK);
//canvasGameScreenextended.drawColor(Color.BLACK);
gameScreenextended.eraseColor(Color.TRANSPARENT); // Make top layer transparent
}
}
Here's the code for the screen class (the getCurrentScreen() method returns a screen object):
public class Screen {
public final String classTAG = this.getClass().getSimpleName();
protected final Game game;
protected final Graphics graphics;
protected Screen(Game game) {
this.game = game;
this.graphics = game.getGraphics();
//game.getInput().reset();
}
public void update() {
}
public void render() {
}
/** Initialize all the sensory that should be used within this screen.*/
public void resume() {
}
public void pause() {
game.getInput().useAccelerometer(false);
game.getInput().useKeyboard(false);
game.getInput().useTouchscreen(false);
}
public void onDispose() {
game.getGraphics().clearFrameBuffer();
}
public void setScreenResizeType(int screenResizeType) {
}
The Screen class is extended and the render() method is shadowed with methods like:
graphics.getCanvasGameScreen().drawRect(play, red);
The funny thing is, when I override the render() method and don't place any code in it, the logger fires constantly with the message: "Game thread is only updating the update method and is not rendering anything". What kind of sorcery is this?!
Help is hugely appreciated!
As far as I understand from your updated post, there is no rendering problem actually. Instead, your code mistakenly prints that message.
This is because you check if(sleepTime > 0) , so if the rendering is very fast and sleepTime is zero, you get that message. Just change it to if(sleepTime >= 0).

Categories

Resources