javaFX - creating smooth animation without creating a new Thread - java

I have a car in my javaFX project where the position of the car(Node) should change(the car should jump smoothly) when SPACE is pressed . so I have used an event handler which invokes a method named :moveUp()
scene.setOnKeyPressed(new EventHandler<KeyEvent>() {
#Override
public void handle(KeyEvent event) {
switch (event.getCode())
{
case SPACE:
moveUp();
break;
}
}
});
This creates a new Thread where the speed of the car is changed 10 times with an interval of 75 milliseconds.
private void moveUp() {
new Thread(new Runnable() {
#Override
public void run() {
carSpeed = 10;
for(;carSpeed>=0;carSpeed--)
{
try {
Thread.currentThread().sleep(75);
} catch (InterruptedException e) {
e.printStackTrace();
}
carPosition_X+=carSpeed;
carPosition_Y-=carSpeed;
car.relocate(carPosition_X,carPosition_Y);
}
for(carSpeed=0;carSpeed<=10;carSpeed++)
{
try {
Thread.currentThread().sleep(75);
} catch (InterruptedException e) {
e.printStackTrace();
}
carPosition_X+=carSpeed;
carPosition_Y+=carSpeed;
car.relocate(carPosition_X,carPosition_Y);
}
}
}).start();
}
This code is doing like this (pressing SPACE once and jumping the car):
If I don't use a different thread the GUI thread will be freeze and if I don't use Thread.sleep() the car will jump abruptly(not smoothly). This code is doing well . But I have learnt that thread.start() doesn't guarantee immediate execution of the thread. How can I guarantee immediate execution ?

I would suggest to stay in the FX Application Thread and to use the class AnimationTimer. Here is a short demo for a smooth jump:
private void moveUp() {
new AnimationTimer() {
long startTime = -1;
double initCarPosition_Y;
#Override
public void handle(long now) {
if(startTime == -1){
startTime = now;
initCarPosition_Y = carPosition_Y;
carSpeedX = 3d;
carSpeedY = -15d;
}
double time = (now - startTime) / 1E9d;
carPosition_X += carSpeedX * time;
carPosition_Y += carSpeedY * time;
if(carSpeed > 0 && initCarPosition_Y <= carPosition_Y){
carPosition_Y = initCarPosition_Y;
stop();
}
carSpeedY += 0.8d * time; //gravity
car.relocate(carPosition_X, carPosition_Y);
}
}.start();
}
This approach gives you full and direct control over what happens in every single frame. However, javaFX also provides high level animation classes including predefined interpolators and transitions. Suitable for alternative approaches could be the following classes:
PathTransition: Allows you to define points and curves which a given node is animated along.
TimeLine: Allows you to define arbitrary animation key frames based on properties like the position of a node.
Note that generally working with these high level classes could become challenging when you want to animate an user controlled actor like your car. These classes all take an exact duration for the animation to last. For example when you want to translate a node as long as a specific key is pressed, you don't know the duration of the animation in beforehand.

Related

Where should I put the game loop in the swing app?

I'm trying to make a simple 2D game in Java.
As far as I know, my game should consist of two threads: "event dispatch thread" (for GUI operations) and "game thread" (for game loop).
I created an outline but could not find where to place the game loop.
in short, I'm trying to create a game loop without freezing my UI thread.
I would be grateful if you could give any information about the things I did wrong.
That's my game loop (You can also give tips to create a better game loop):
while(true) {
repaint();
try {
Thread.sleep(17);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
import java.awt.Color;
import java.awt.Graphics;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class Main {
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
createAndShowGUI();
}
});
}
private static void createAndShowGUI() {
JFrame frame = new JFrame("Forge and Attack");
frame.setExtendedState(JFrame.MAXIMIZED_BOTH);
frame.setUndecorated(true);
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setFocusable(true);
frame.add(new MyPanel());
}
}
class MyPanel extends JPanel implements KeyListener, MouseListener {
public MyPanel() {
setBackground(Color.BLACK);
setOpaque(true);
addKeyListener(this);
addMouseListener(new MouseAdapter(){
public void mousePressed(MouseEvent e){
}
});
}
#Override
public void paint(Graphics g) {
}
}
I thought this an interesting topic to expand on... I have covered the questions you asked as well as showed the maybe a better or correct way of doing certain things like painting, and listening for keys pressed, as well as some others like separation of concerns and making the entire game more reusable/expandable.
1. Where to place the game loop?
So this isn't straight forward and can depend on each individuals coding style, but really all we seek to achieve here is to create the game loop and start it at an appropriate time. I believe code speaks a 1000 words (sometimes it might just be 1000 words :)), but below is some code which in the most minimally possible way (still producing a valid working example) shows where a game loop can be created/placed and used in the code, the code is heavily commented for clarity and understanding:
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.ArrayList;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.ImageIO;
import javax.swing.AbstractAction;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;
public class MyGame {
private Scene scene;
private Sprite player;
private Thread gameLoop;
private boolean isRunning;
public MyGame() {
createAndShowUI();
}
public static void main(String[] args) {
SwingUtilities.invokeLater(MyGame::new);
}
/**
* Here we will create our swing UI as well as initialise and setup our
* sprites, scene, and game loop and other buttons etc
*/
private void createAndShowUI() {
JFrame frame = new JFrame("MyGame");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
player = new Sprite(/*ImageIO.read(getClass().getResourceAsStream("...."))*/);
this.scene = new Scene();
this.scene.add(player);
this.addKeyBindings();
this.setupGameLoop();
frame.add(scene);
frame.pack();
frame.setVisible(true);
// after setting the frame visible we start the game loop, this could be done in a button or wherever you want
this.isRunning = true;
this.gameLoop.start();
}
/**
* This method would actually create and setup the game loop The game loop
* will always be encapsulated in a Thread, Timer or some sort of construct
* that generates a separate thread in order to not block the UI
*/
private void setupGameLoop() {
// initialise the thread
gameLoop = new Thread(() -> {
// while the game "is running" and the isRunning boolean is set to true, loop forever
while (isRunning) {
// here we do 2 very important things which might later be expanded to 3:
// 1. We call Scene#update: this essentially will iterate all of our Sprites in our game and update their movments/position in the game via Sprite#update()
this.scene.update();
// TODO later on one might add a method like this.scene.checkCollisions in which you check if 2 sprites are interesecting and do something about it
// 2. We then call JPanel#repaint() which will cause JPanel#paintComponent to be called and thus we will iterate all of our sprites
// and invoke the Sprite#render method which will draw them to the screen
this.scene.repaint();
// here we throttle our game loop, because we are using a while loop this will execute as fast as it possible can, which might not be needed
// so here we call Thread#slepp so we can give the CPU some time to breathe :)
try {
Thread.sleep(15);
} catch (InterruptedException ex) {
}
}
});
}
private void addKeyBindings() {
// here we would use KeyBindings (https://docs.oracle.com/javase/tutorial/uiswing/misc/keybinding.html) and add them to our Scene/JPanel
// these would allow us to manipulate our Sprite objects using the keyboard below is 2 examples for using the A key to make our player/Sprite go left
// or the D key to make the player/Sprite go to the right
this.scene.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_A, 0, false), "A pressed");
this.scene.getActionMap().put("A pressed", new AbstractAction() {
#Override
public void actionPerformed(ActionEvent e) {
player.LEFT = true;
}
});
this.scene.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_A, 0, true), "A released");
this.scene.getActionMap().put("A released", new AbstractAction() {
#Override
public void actionPerformed(ActionEvent e) {
player.LEFT = false;
}
});
this.scene.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_D, 0, false), "D pressed");
this.scene.getActionMap().put("D pressed", new AbstractAction() {
#Override
public void actionPerformed(ActionEvent e) {
player.RIGHT = true;
}
});
this.scene.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_D, 0, true), "D released");
this.scene.getActionMap().put("D released", new AbstractAction() {
#Override
public void actionPerformed(ActionEvent e) {
player.RIGHT = false;
}
});
}
public class Scene extends JPanel {
private final ArrayList<Sprite> sprites;
public Scene() {
// we are using a game loop to repaint, so probably dont want swing randomly doing it for us
this.setIgnoreRepaint(true);
this.sprites = new ArrayList<>();
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
// this method gets called on Scene#repaint in our game loop and we then render each in our game
sprites.forEach((sprite) -> {
sprite.render(g2d);
});
}
#Override
public Dimension getPreferredSize() {
// because no components are added to the JPanel, we will have a default sizxe of 0,0 so we instead force the JPanel to a size we want
return new Dimension(500, 500);
}
public void add(Sprite go) {
this.sprites.add(go);
}
private void update() {
// this method gets called on Scene#update in our game loop and we then update the sprites movement and position our game
sprites.forEach((go) -> {
go.update();
});
}
}
public class Sprite {
private int x = 50, y = 50, speed = 5;
//private final BufferedImage image;
public boolean LEFT, RIGHT, UP, DOWN;
public Sprite(/*BufferedImage image*/) {
//this.image = image;
}
public void render(Graphics2D g2d) {
//g2d.drawImage(this.image, this.x, this.y, null);
g2d.fillRect(this.x, this.y, 100, 100);
}
public void update() {
if (LEFT) {
this.x -= this.speed;
}
if (RIGHT) {
this.x += this.speed;
}
if (UP) {
this.y -= this.speed;
}
if (DOWN) {
this.y += this.speed;
}
}
}
}
2. Tips to create a better game loop
This very much like the first point in my answer is very subjective to what you are trying to achieve and at what granularity will your problem be satisfied with. So instead of prescribing 1 type of game loop. Let us look at the various kinds we can have:
First what is a game loop?*
The game loop is the overall flow control for the entire game program. It’s a loop because the game keeps doing a series of actions over and over again until the user quits. Each iteration of the game loop is known as a frame. Most real-time games update several times per second: 30 and 60 are the two most common intervals. If a game runs at 60 FPS (frames per second), this means that the game loop completes 60 iterations every second.
a. While loop
This we have seen in the above example and is simply a while loop encapsulated inside a Thread with possibly a Thread#sleep call to help throttle CPU usage. This and the Swing Timer are probably the most basic you can use.
gameLoop = new Thread(() -> {
while (isRunning) {
this.scene.update();
this.scene.repaint();
try {
Thread.sleep(15);
} catch (InterruptedException ex) {
}
}
});
Pros:
Easy to implement
All updating and rendering, repainting is done in a separate thread from the EDT
Cons:
Cannot guarantee the same frame rate on various PCs, so movement of the game might look better/worse or slower/faster on various computers depending on the hardware
b. Swing Timer
Similar to the while loop, a Swing Timer can be used in which an action event is fired periodically, because it is fired periodically we can simply use an if statement to check if the game is running and then call our necessary methods
gameLoop = new Timer(15, (ActionEvent e) -> {
if (isRunning) {
MyGame.this.scene.update();
MyGame.this.scene.repaint();
}
});
Pros:
Easy to implement
Cons:
Runs on the EDT which is not necessary or wanted as we are not updating any Swing components but rather simply painting to it
Cannot guarantee the same frame rate on various PCs, so movement of the game might look better/worse or slower/faster on various computers depending on the hardware
c. Fixed time step*
This is a more complex game loop (but simpler than a variable time step game loop). This works on the premise that we want to achieve a specific FPS i.e. 30 or 60 frames per second, and thus we simply make sure we call our update and rendering methods that exact number of times per seconds.
Update methods do not accept a "time elapsed", as they assume each update is for a fixed time period. Calculations may be done as position += distancePerUpdate. The example includes an interpolation during render.
gameLoop = new Thread(() -> {
//This value would probably be stored elsewhere.
final double GAME_HERTZ = 60.0;
//Calculate how many ns each frame should take for our target game hertz.
final double TIME_BETWEEN_UPDATES = 1000000000 / GAME_HERTZ;
//If we are able to get as high as this FPS, don't render again.
final double TARGET_FPS = 60;
final double TARGET_TIME_BETWEEN_RENDERS = 1000000000 / TARGET_FPS;
//At the very most we will update the game this many times before a new render.
//If you're worried about visual hitches more than perfect timing, set this to 1.
final int MAX_UPDATES_BEFORE_RENDER = 5;
//We will need the last update time.
double lastUpdateTime = System.nanoTime();
//Store the last time we rendered.
double lastRenderTime = System.nanoTime();
while (isRunning) {
double now = System.nanoTime();
int updateCount = 0;
//Do as many game updates as we need to, potentially playing catchup.
while (now - lastUpdateTime > TIME_BETWEEN_UPDATES && updateCount < MAX_UPDATES_BEFORE_RENDER) {
MyGame.this.scene.update();
lastUpdateTime += TIME_BETWEEN_UPDATES;
updateCount++;
}
//If for some reason an update takes forever, we don't want to do an insane number of catchups.
//If you were doing some sort of game that needed to keep EXACT time, you would get rid of this.
if (now - lastUpdateTime > TIME_BETWEEN_UPDATES) {
lastUpdateTime = now - TIME_BETWEEN_UPDATES;
}
//Render. To do so, we need to calculate interpolation for a smooth render.
float interpolation = Math.min(1.0f, (float) ((now - lastUpdateTime) / TIME_BETWEEN_UPDATES));
MyGame.this.scene.render(interpolation);
lastRenderTime = now;
//Yield until it has been at least the target time between renders. This saves the CPU from hogging.
while (now - lastRenderTime < TARGET_TIME_BETWEEN_RENDERS && now - lastUpdateTime < TIME_BETWEEN_UPDATES) {
//allow the threading system to play threads that are waiting to run.
Thread.yield();
//This stops the app from consuming all your CPU. It makes this slightly less accurate, but is worth it.
//You can remove this line and it will still work (better), your CPU just climbs on certain OSes.
//FYI on some OS's this can cause pretty bad stuttering. Scroll down and have a look at different peoples' solutions to this.
//On my OS it does not unpuase the game if i take this away
try {
Thread.sleep(1);
} catch (Exception e) {
}
now = System.nanoTime();
}
}
});
This loop will need other changes such to take place to allow for interpolation:
Scene:
public class Scene extends JPanel {
private float interpolation;
#Override
protected void paintComponent(Graphics g) {
...
sprites.forEach((sprite) -> {
sprite.render(g2d, this.interpolation);
});
}
public void render(float interpolation) {
this.interpolation = interpolation;
this.repaint();
}
}
Sprite:
public class Sprite {
public void render(Graphics2D g2d, float interpolation) {
g2d.fillRect((int) (this.x + interpolation), (int) (this.y + interpolation), 100, 100);
}
}
Pros:
predictable, deterministic FPS on various computers/hardware
clearer calculation code
Cons:
not synced to monitor v-sync (causes jittery graphics unless you interpolate) - this example interpolates
limited max frame rate (unless you interpolate) - this example interpolates
d. Variable time step*
Usually used when a physics system is being implemented, or whenever a record of elapsed time is needed, I.e. animations. Physics/animation updates are passed a "time elapsed since last update" argument and are hence framerate-dependent. This may mean doing calculations as position += distancePerSecond * timeElapsed.
gameLoop = new Thread(() -> {
// how many frames should be drawn in a second
final int FRAMES_PER_SECOND = 60;
// calculate how many nano seconds each frame should take for our target frames per second.
final long TIME_BETWEEN_UPDATES = 1000000000 / FRAMES_PER_SECOND;
// track number of frames
int frameCount;
// if you're worried about visual hitches more than perfect timing, set this to 1. else 5 should be okay
final int MAX_UPDATES_BETWEEN_RENDER = 1;
// we will need the last update time.
long lastUpdateTime = System.nanoTime();
// store the time we started this will be used for updating map and charcter animations
long currTime = System.currentTimeMillis();
while (isRunning) {
long now = System.nanoTime();
long elapsedTime = System.currentTimeMillis() - currTime;
currTime += elapsedTime;
int updateCount = 0;
// do as many game updates as we need to, potentially playing catchup.
while (now - lastUpdateTime >= TIME_BETWEEN_UPDATES && updateCount < MAX_UPDATES_BETWEEN_RENDER) {
MyGame.this.scene.update(elapsedTime);//Update the entity movements and collision checks etc (all has to do with updating the games status i.e call move() on Enitites)
lastUpdateTime += TIME_BETWEEN_UPDATES;
updateCount++;
}
// if for some reason an update takes forever, we don't want to do an insane number of catchups.
// if you were doing some sort of game that needed to keep EXACT time, you would get rid of this.
if (now - lastUpdateTime >= TIME_BETWEEN_UPDATES) {
lastUpdateTime = now - TIME_BETWEEN_UPDATES;
}
MyGame.this.scene.repaint(); // draw call for rendering sprites etc
long lastRenderTime = now;
//Yield until it has been at least the target time between renders. This saves the CPU from hogging.
while (now - lastRenderTime < TIME_BETWEEN_UPDATES && now - lastUpdateTime < TIME_BETWEEN_UPDATES) {
Thread.yield();
now = System.nanoTime();
}
}
});
Scene:
public class Scene extends JPanel {
private void update(long elapsedTime) {
// this method gets called on Scene#update in our game loop and we then update the sprites movement and position our game
sprites.forEach((go) -> {
go.update(elapsedTime);
});
}
}
Sprite:
public class Sprite {
private float speed = 0.5f;
public void update(long elapsedTime) {
if (LEFT) {
this.x -= this.speed * elapsedTime;
}
if (RIGHT) {
this.x += this.speed * elapsedTime;
}
if (UP) {
this.y -= this.speed * elapsedTime;
}
if (DOWN) {
this.y += this.speed * elapsedTime;
}
}
}
Pros:
smooth
easier to to code
Cons:
non-deterministic, unpredictable at very small or large steps

Java Application is not responding after using Thread.Sleep();

I am currently having some trouble when running the follwing code. If I delete this part the problems disappear so this part of my whole code has to be the problem. It runs and draws what I want perfectly but after a few seconds (maxAddedRuntime is set via user (milliseconds)) the application freezes for a while (window is not responding Windows message) and starts over with drawing after waiting approximately the same time while the window is frozen. What do I do wrong?
I am using SWT and a canvas to draw. Thank you for your help
public void drawNetwork(Canvas canvas, GC gc, Network network, Shell shlNetworkVisualizer) {
startTime = System.currentTimeMillis();
endTime = startTime + maxAddedRuntime;
this.drawNetworkAlg1(canvas, gc, network);
int canvasHeight = canvas.getBounds().height;
int canvasWidth = canvas.getBounds().width;
while (System.currentTimeMillis()<endTime) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
gc.fillRectangle(0, 0, canvasWidth, canvasHeight); //ClearCanvas basically
for (Nodek: network.node) {
//drawSomeStuff
}
for (Edge k: network.edges) {
//alsoDrawSomeStuff
}
}
}
An SWT app must return to the main Display.readAndDispatch loop as quickly as possible. So you cannot use a loop with a Thread.sleep call - this will just lock up the UI until the loop ends.
Instead you can use Display.timerExec to run code after a delay. You would use this to run a single step (just one gc.fillRectange for example) and then call Display.timerExec again to schedule the next step.
public void timerExec(int milliseconds, Runnable runnable)
Note: The GC you receive from a paint event is only valid during the paint. The timerExec call should normally just call redraw on the canvas to cause a new paint event to be generated.
Here is a simple class that does basic timerExec calls and paints:
class Progress
{
private final Canvas canvas;
private final long endTime;
Progress(Canvas c)
{
canvas = c;
endTime = System.currentTimeMillis() + 1_000;
canvas.addListener(SWT.Paint, this::paint);
canvas.getDisplay().timerExec(100, this::timer);
}
private void paint(Event event)
{
GC gc = event.gc;
int canvasHeight = canvas.getBounds().height;
int canvasWidth = canvas.getBounds().width;
gc.fillRectangle(0, 0, canvasWidth, canvasHeight);
// TODO more painting
}
private void timer()
{
if (canvas.isDisposed()) { // Don't continue if control closed
return;
}
canvas.redraw();
if (System.currentTimeMillis() < endTime) {
canvas.getDisplay().timerExec(100, this::timer);
}
}
}

Java - ExecutorService Cancelling Overhead

I am writing a program that generates images in response to user interaction (panning). For each image I create a task and submit this to a ThreadPool. To make the panning more responsive I create two versions of the image needed, one low resolution one and then a high resolution one. The high resolution image can take more than a second to calculate and if the user is panning a lot I do not want to bog down the processor with calculating old high res images. To manage this I have created an UpdateHandler (this is an inner class) in my program that is supposed to sort this out.
On occation some processes that should have been cancelled slip through. What am I doing wrong, why is this not thread safe? I am not great at multi-threading so any tips are welcome.
Updatehandler:
private class UpdateHandler {
private boolean cancelled = false;
private int number;
private Future<WritableImage> future;
private ChangeListener<Number> listener = new ChangeListener<Number>() {
#Override
public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number newValue) {
if(newValue.intValue() > number) {
cancel();
}
}
};
private UpdateHandler() {
number = numberOfUpdaters.get() + 1;
numberOfUpdaters.set(number);
numberOfUpdaters.addListener(listener);
if(number < numberOfUpdaters.get()) {//If the number was increased before the listener was applied.
cancel();
}
updateLowResImage();
int resolution = (int) rightPane.resolutionField.getValue();
if(resolution > lowResolution && !isCancelled()) {
new Thread(new Runnable() {
#Override
public void run() {
EnhancedCallable<WritableImage> task = getImageTask(resolution, resolution);
task.setDescription("Generating " + name);
future = threadPool.submit(task);
if(isCancelled()) {//If cancelled before construction but after check cancel again.
cancel();
}
try {
WritableImage image = future.get();
if(image != null) {
setImage(image);
}
} catch (ExecutionException | InterruptedException | CancellationException e) {
//Do nothing.
} finally {//When we are done cancel to make sure the listener is removed.
cancel();
}
}
}).start();
}
}
/**
* Cancels the future or prevents it from starting. Can be called several times without problems.
*/
private void cancel() {
cancelled = true;
try {
if(!future.isDone() && !future.isCancelled()) {//If Future is still active.
future.cancel(true);
}
if(future.isDone() || future.isCancelled()) {//If Future is not active anymore.
numberOfUpdaters.removeListener(listener);
}
} catch(NullPointerException e) {//If the Future is not yet initialised.
numberOfUpdaters.removeListener(listener);
}
}
private boolean isCancelled() {
return cancelled;
}
}

Java Thread.sleep() causing JFrame to flicker when resized

So I have a game that I'm trying to make and in the game loop, I call Thread.sleep(). Else where, I have code that maintains the aspect ratio of the window when resizing. This works great, except that I get weird flickering when I'm resizing. I've narrowed the problem down to Thread.sleep(), when I take this line out, my program works just as expected, but this causes the CPU to spike so high that on my Macbook, the Activity Monitor app says my game is using 170+%! Now this is problematic and exactly why I put the sleep line in there anyway. I've heard that sleeping on the event dispatch thread will cause this effect, but I am running this loop in a new thread, so I thought I was good. Do you guys know what could be going on? Here's part of the source code (you really need to look at the run() method):
package jeffrey_ryan.game2d;
public class GameLoop implements Runnable {
private boolean running = false;
private boolean paused = false;
private float gameHertz = 30.0f;
private long timeBetweenUpdates = (long) (1_000_000_000 / gameHertz);
private int maxUpdates = 5;
private LoopListener loopListener;
public void run() {
long lastUpdateTime = System.nanoTime();
running = true;
while (running) {
long now = System.nanoTime();
if (!paused) {
int updates = 0;
while (now - lastUpdateTime >= timeBetweenUpdates && updates < maxUpdates) {
if (loopListener != null) {
loopListener.update((double) timeBetweenUpdates / 1_000_000_000);
}
lastUpdateTime += timeBetweenUpdates;
updates++;
}
if (loopListener != null) {
float interpolation = Math.min(1.0f, (float) (now - lastUpdateTime) / timeBetweenUpdates);
loopListener.render(interpolation);
}
long timeRemaining = (timeBetweenUpdates - (now - lastUpdateTime)) / 1_000_000;
try {
Thread.sleep(Math.max(timeRemaining - 5, 0)); // HERE'S THE OFFENDING LINE ******************
}
catch (InterruptedException ie) {
ie.printStackTrace();
}
}
else {
try {
Thread.sleep(25);
}
catch (InterruptedException ie) {
ie.printStackTrace();
}
}
}
}
public void start() {
running = true;
}
public void stop() {
running = false;
}
public void pause() {
paused = true;
}
public void play() {
paused = false;
}
public float getSpeed() {
return gameHertz;
}
public void setSpeed(float hertz) {
gameHertz = hertz;
timeBetweenUpdates = (long) (1_000_000_000 / gameHertz);
}
public int getMaxUpdates() {
return maxUpdates;
}
public void setMaxUpdates(int updates) {
maxUpdates = updates;
}
public void setLoopListener(LoopListener listener) {
loopListener = listener;
}
}
In my subclass of JPanel, here's the code that runs this loop (Where the loop variable is an instance of the above class):
#Override
public void addNotify() {
super.addNotify();
addKeyListener(this);
addMouseListener(this);
Thread thread = new Thread(loop, "GameLoop");
thread.start();
}
If you guys could help me I would love it, I'm really stumped. Thanks!
You should use SwingWorker instead of a Thread to manipulate Swing components asynchronously. When I discovered this guy my life changed =). The SwingWorker gives you a method "process" which you can use to make actions gradually, and a "done" method to finish your processing, both of these methods are safe to handle the event dispatch thread. The background process you should make on "doInBackground".
Calling 'Thread.sleep(n)' causes the whole thread to become unresponsive, if this thread is tied to your JFrame thread then that thread will also become unresponsive and cause the whole frame and component to freeze and stop responding -- probably the reason for the flickering. So make sure the sleep is in game loop and not on the frame, one way to do this is create two threads at initialization, one for the frame and the other for the logic, then just let the game loop handle input and output while the display thread simply displays (i believe this how most game engines work). Also make sure neither thread is linked in any or the sleeping thread will affect the display thread.
I found the answer to my problem. The class that was calling the loop, which was a JPanel, didn't repaint when resized, only when the loop told it to, which caused some periods where the JPanel wasn't painted too. I fixed this by overriding paintComponent.

JLabel.setText() is only setting text for last element in a loop

First of all, apologies for how long winded this is.
I'm trying to make a simple roulette game that allows a user to add players, place bets for these players, and spin the roulette wheel, which is represented as a simple JLabel that updates it's text with each number it passes.
However, I've run into a bug that I'm having a lot of trouble with: the JLabel only updates the text for the last element in my loop.
Basically, my solution works like this:
When a user presses a button labelled "Spin" (given that users have been added to the game), I call a method from a class called SpinWheelService, which is an Observable singleton which in turn calls the notifyObservers() method:
public void actionPerformed(ActionEvent e) {
String cmd = e.getActionCommand();
String description = null;
if (ADD_PLAYER.equals(cmd)) {
addDialog();
} else if (PLACE_BET.equals(cmd)) {
betDialog();
} else if (SPIN.equals(cmd)) {
SpinWheelService.sws.setSpinWheelService();
} else if (DISPLAY.equals(cmd)) {
System.out.println("Display selected!");
}
}
Here is my SpinWheelService class:
package model;
import java.util.*;
public class SpinWheelService extends Observable {
public static SpinWheelService sws = new SpinWheelService();
public void setSpinWheelService() {
setChanged();
notifyObservers();
}
}
The only listener registered for SpinWheelService is this class, where GameEngine is my game engine that handles internal game logic, WheelCallbackImpl is a class that updates the View:
class SpinWheelObserver implements Observer {
GameEngine gameEngine;
ArrayList<SimplePlayer> players;
WheelCallbackImpl wheelCall;
int n;
public SpinWheelObserver(GameEngine engine, WheelCallbackImpl wheel, ArrayList<SimplePlayer> playerList) {
players = playerList;
gameEngine = engine;
wheelCall = wheel;
}
public void update(Observable sender, Object arg) {
// check if any players are present
if (players.size() == 0) {
System.out.println("Empty player array!");
return;
}
do {
gameEngine.spin(40, 1, 300, 30, wheelCall);
n = wheelCall.playback();
} while (n== 0);
}
}
The main point of note here is my gameEngine.spin() method, which is this:
public class GameEngineImpl implements GameEngine {
private List<Player> playerList = new ArrayList<Player>();
// method handles the slowing down of the roulette wheel, printing numbers at an incremental delay
public void delay(int millis) {
try {
Thread.sleep(millis);
} catch (InterruptedException e) {
System.out.println("Sleep method failed.");
}
}
public void spin(int wheelSize, int initialDelay, int finalDelay,
int delayIncrement, WheelCallback callback) {
Random rand = new Random();
int curNo = rand.nextInt(wheelSize) + 1;
int finalNo = 0;
assert (curNo >= 1);
// while loop handles how long the wheel will spin for
while (initialDelay <= finalDelay) {
delay(initialDelay);
initialDelay += delayIncrement;
// handles rotating nature of the wheel, ensures that if it reaches wheel size, reverts to 1
if (curNo > wheelSize) {
curNo = 1;
callback.nextNumber(curNo, this);
curNo++;
}
assert (curNo <= wheelSize);
callback.nextNumber(curNo, this);
curNo++;
finalNo = curNo - 1;
}
calculateResult(finalNo);
callback.result(finalNo, this);
}
The method callback.nextNumber(curNo, this):
public void nextNumber(int nextNumber, GameEngine engine) {
String strNo = Integer.toString(nextNumber);
assert (nextNumber >= 1);
System.out.println(nextNumber);
wcWheel.setCounter(strNo);
}
Where in, wcWheel is my singleton instance of my View, which contains the method setCounter():
public void setCounter(String value) {
label.setText(value);
}
Sorry for how convoluted my explanation is, but basically what it boils down to is that setCounter() is definitely being called, but seems to only call the setText() method on the final number. So what I'm left with is an empty label that doesn't present the number until the entire roulette has finished spinning.
I've determined that setCounter() runs on the event dispatch thread, and I suspect this is a concurrency issue but I have no idea how to correct it.
I've tried to include all relevant code, but if I'm missing anything, please mention it and I'll post it up as well.
I'm at my wits end here, so if anyone would be kind of enough to help, that would be so great.
Thank you!
Your while loop along Thread.sleep() will block and repainting or changing of the UI until the loop is finished.
Instead you'll want to implement a javax.swing.Timer for the delay, and keep a counter for the number of ticks, to stop it. You can see more at How to Use Swing Timers
The basic construct is
Timer ( int delayInMillis, ActionListener listener )
where delayInMillis is the millisecond delay between firing of an ActionEvent. This event is listened for by the listener. So every time the event is fired, the actionPerfomed of the listener is called. So you might do something like this:
Timer timer = new Timer(delay, new ActionListener()(
#Override
public void actionPerformed(ActionEvent e) {
if (count == 0) {
((Timer)e.getSource()).stop();
} else {
//make a change to your label
count--;
}
}
));
You can call timer.start() to start the timer. Every delay milliseconds, the label will change to what you need it to, until some arbitrary count reaches 0, then timer stops. You can then set the count variable to whatever you need to, if you want to to be random, say depending on how hard the wheel is spun :D
I think you didn't post all the relevant code that is required to know exactly the problem.
But most likely the problem is due to you run your loop and JLabel.setText() in the EDT (Event Dispatching Thread).
Note that updating the UI components (e.g. the text of a JLabel) also happens in the EDT, so while your loop runs in the EDT, the text will not be updated, only after your loop ended and you return from your event listener. Then since you modified the text of the JLabel it will be refreshed / repainted and you will see the last value you set to it.
Example to demonstrate this. In the following example a loop in the event listener loops from 0 to 9 and sets the text of the label, but you will only see the final 9 be set:
JPanel p = new JPanel();
final JLabel l = new JLabel("-1");
p.add(l);
JButton b = new JButton("Loop");
p.add(b);
b.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
for ( int i = 0; i < 10; i++ ) {
l.setText( "" + i );
try { Thread.sleep( 200 ); } catch ( InterruptedException e1 ) {}
}
}
} );
A proposed solution: Use javax.swing.Timer to do the loop's work. Swing's timer calls its listeners in the EDT so it's safe to update swing components in it, and once the listener returns, a component UI update can happen immediately:
JPanel p = new JPanel();
final JLabel l = new JLabel("-1");
p.add(l);
JButton b = new JButton("Loop");
p.add(b);
b.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
new Timer(200, new ActionListener() {
int i = 0;
#Override
public void actionPerformed(ActionEvent e2) {
l.setText("" + i);
if ( ++i == 10 )
((Timer)e2.getSource()).stop();
}
}).start();
}
} );
In this solution you will see the label's text counting from 0 up to 9 nicely.
It's appears to me that your entire game must block in the action handler until the while loop has finished? So the text of the label will be getting updated but only the last update will be visible once the AWT thread is running again.

Categories

Resources