JavaFX: Threads, refreshing the view - java

I'm making Tower Defence Game using JavaFX, my current problem is updating view after animation calculations. I've tried making new Thread and starting it, but whenever i touch GraphicContext from Canvas, game crashed. My current game loop class looks like:
import model.Enemy;
import model.Map;
import model.Player;
import model.Model;
import view.View;
public class GameLoop2 {
private Canvas canvas;
public Integer enemiesNr;
public Integer enemiesSpawnTime;
public Integer spawnedEnemies;
private long lastSpawn;
private ArrayList<Enemy> enemies;
private boolean firstPlay;
private Model model;
private Map map;
public GameLoop2(Canvas mainCanvas, Model m) {
model = m;
map = model.getMap();
canvas = mainCanvas;
enemiesNr = map.getEnemiesNr();
enemiesSpawnTime = model.getMap().getEnemiesSpawnTime();
spawnedEnemies = 0;
lastSpawn = System.currentTimeMillis();
enemies = new ArrayList<>(enemiesNr);
for(int i=0; i < enemiesNr;i++)
enemies.add(i, new Enemy(map.getStartXPosition(), map.getStartXPosition()));
}
private void spawnEnemy() {
if(spawnedEnemies >= enemiesNr)
return;
enemies.get(spawnedEnemies).setAlive(true);
View.drawEnemy(canvas, enemies.get(spawnedEnemies));
spawnedEnemies++;
lastSpawn = System.currentTimeMillis();
}
private void enemyPhysics() {
for(Enemy e: enemies)
if(e.isAlive())
e.physics(1);
}
private void drawEnemies(){
for(Enemy e: enemies)
if(e.isAlive())
View.drawEnemy(canvas,e);
}
private void update() {
canvas.getGraphicsContext2D().restore();
View.drawMap(map, canvas);
drawEnemies();
}
public void start() {
while(true){
// Calculations
long now = System.currentTimeMillis();
if (now - lastSpawn > enemiesSpawnTime) {
spawnEnemy();
}
enemyPhysics();
// Updating View
update();
// View is refreshed after break; statement
if(now - lastSpawn > 6000)
break;
}
}
I've also tried Service class, but it didn't worked for me. I would also want to make claculation as fast as possible to make movement animation effect for aproaching enemies. It will be nice to make it in a way that will allow to add another thread, for example background music, or calculating damage to main tower.

Related

JavaFX Timeline locked at 1fps

i have a problem with the Timeline in JavaFX : the Timeline is locked at 1fps.
KeyFrames aren't triggerred more than this, even if i've put three keyframes :
60 times per second
120 times per second
1 time per second
They're all triggered at the same time : 1 second
TickSystem class :
package TickSystem;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.shape.Rectangle;
import javafx.util.Duration;
public class TickSystem implements EventHandler<ActionEvent> {
private KeyFrame kfU; // update
private KeyFrame kfD; // draw
private KeyFrame kfFPS; // FPS count
public Rectangle r;
public int curFrame = 0;
public int tick = 0;
public final Timeline gameLoop = new Timeline(120);
public final Duration updateTime = Duration.millis((double)1000/60); // 60 times per seconds
public final Duration drawTime = Duration.millis((double)1000/120); // 120 times per seconds
public int fps;
private int lastFrames = 0;
public TickSystem(Rectangle r){
this.r = r;
this.kfU = new KeyFrame(updateTime,"tickKeyUpdate", this::handle);
this.kfD = new KeyFrame(drawTime,"tickKeyDraw", this::handleDraw);
this.kfFPS = new KeyFrame(Duration.seconds(1),"tickKeyFPS", this::handleFPS);
this.gameLoop.setCycleCount(Timeline.INDEFINITE);
this.gameLoop.getKeyFrames().add(this.kfU);
this.gameLoop.getKeyFrames().add(this.kfD);
this.gameLoop.getKeyFrames().add(this.kfFPS);
}
public void start(){
this.gameLoop.play();
}
public void pause(){
this.gameLoop.pause();
}
public void stop(){
this.gameLoop.stop();
}
#Override
public void handle(ActionEvent ae) { // for update
this.tick++;
}
public void handleDraw(ActionEvent ae){ // for draw
this.curFrame++;
this.r.setWidth(curFrame);
}
public void handleFPS(ActionEvent ae) { // for FPS
this.fps = this.curFrame - this.lastFrames;
this.lastFrames = this.curFrame;
System.out.println(this.fps);
}
}
Main class :
package TickSystem;
import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
public class Main extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) throws Exception {
primaryStage.setTitle("Data");
primaryStage.setResizable(true);
Group root = new Group();
Scene scene = new Scene(root,400,400);
Rectangle r = new Rectangle(10,10,100,100);
r.setFill(Color.RED);
root.getChildren().add(r);
TickSystem loop = new TickSystem(r);
primaryStage.setScene(scene);
primaryStage.show();
loop.start();
}
}
So, the rectangle must gain 1px on width each time the handleDraw function is called, so 120 times per second.
Actually, he only gain one pixel per second. And i'm at 1fps on the handleFPS function.
This function must print the number of times the handleDraw function has been called each seconds
EDIT :
I've made these three KeyFrames cause i try to make a 2D game.
I've already make a good part of this game on Java with Swing and i need to update infos (like player poisition) 60 times per second but i try to draw informations on screen 120 times per second.
JavaFX sounds better to me for the GUI, so i left java swing behind.
These class are for testing and i'm new on JavaFX. Thanks for your time.
You have three key frames; one at 1/120 second, one at 1/60 second, and one at 1 second. Since the longest duration of any key frame is one second, the duration of one cycle of the timeline is one second.
Therefore, during one cycle of the timeline, the following three things happen:
At 1/120 second, handleDraw() is invoked
At 1/60 second, handle() is invoked
At 1 second, handleFPS() is invoked
So during one cycle of the timeline (1 second), handleDraw() and handle() are invoked once each.
You set the cycle count to INDEFINITE, so once one cycle is completed, it repeats; this happens indefinitely.
One solution is to use a separate timeline for each of the individual tasks. This will not add any appreciable overhead to the application.
(As an aside: there is no point here in implementing EventHandler. You never use an instance of TickSystem as an event handler; you only use the lambda expressions.)
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.event.ActionEvent;
import javafx.scene.shape.Rectangle;
import javafx.util.Duration;
public class TickSystem {
private KeyFrame kfU; // update
private KeyFrame kfD; // draw
private KeyFrame kfFPS; // FPS count
public Rectangle r;
public int curFrame = 0;
public int tick = 0;
public final Timeline gameLoop = new Timeline(60);
private final Timeline drawLoop = new Timeline(120);
private final Timeline fpsLoop = new Timeline(1000);
public final Duration updateTime = Duration.millis((double)1000/60); // 60 times per seconds
public final Duration drawTime = Duration.millis((double)1000/120); // 120 times per seconds
public int fps;
private int lastFrames = 0;
public TickSystem(Rectangle r){
this.r = r;
this.kfU = new KeyFrame(updateTime,"tickKeyUpdate", this::handleUpdate);
this.kfD = new KeyFrame(drawTime,"tickKeyDraw", this::handleDraw);
this.kfFPS = new KeyFrame(Duration.seconds(1),"tickKeyFPS", this::handleFPS);
this.gameLoop.setCycleCount(Timeline.INDEFINITE);
this.drawLoop.setCycleCount(Timeline.INDEFINITE);
this.fpsLoop.setCycleCount(Timeline.INDEFINITE);
this.fpsLoop.getKeyFrames().add(this.kfFPS);
this.gameLoop.getKeyFrames().add(this.kfU);
this.drawLoop.getKeyFrames().add(this.kfD);
}
public void start(){
this.gameLoop.play();
this.fpsLoop.play();
this.drawLoop.play();
}
public void pause(){
this.gameLoop.pause();
this.fpsLoop.pause();
this.drawLoop.pause();
}
public void stop(){
this.gameLoop.stop();
this.drawLoop.stop();
this.fpsLoop.stop();
}
public void handleUpdate(ActionEvent ae) { // for update
this.tick++;
}
public void handleDraw(ActionEvent ae){ // for draw
this.curFrame++;
this.r.setWidth(curFrame);
}
public void handleFPS(ActionEvent ae) { // for FPS
this.fps = this.curFrame - this.lastFrames;
this.lastFrames = this.curFrame;
System.out.println(this.fps);
}
}
Or, more succinctly:
public class TickSystem {
private Rectangle r;
private int curFrame = 0;
private int tick = 0;
private final List<Timeline> timelines = new ArrayList<>();
private int fps;
private int lastFrames = 0;
public TickSystem(Rectangle r){
this.r = r;
timelines.add(createTimeline(60, this::handleUpdate));
timelines.add(createTimeline(120, this::handleDraw));
timelines.add(createTimeline(1, this::handleFPS));
}
private Timeline createTimeline(int frequency, EventHandler<ActionEvent> handler) {
Timeline timeline = new Timeline(frequency);
timeline.getKeyFrames().add(new KeyFrame(Duration.millis(1000.0 / frequency), handler));
timeline.setCycleCount(Animation.INDEFINITE);
return timeline;
}
public void start(){
timelines.forEach(Timeline::play);
}
public void pause(){
timelines.forEach(Timeline::pause);
}
public void stop(){
timelines.forEach(Timeline::stop);
}
public void handleUpdate(ActionEvent ae) { // for update
this.tick++;
}
public void handleDraw(ActionEvent ae){ // for draw
this.curFrame++;
this.r.setWidth(curFrame);
}
public void handleFPS(ActionEvent ae) { // for FPS
this.fps = this.curFrame - this.lastFrames;
this.lastFrames = this.curFrame;
System.out.println(this.fps);
}
}
Another solution would be to use an AnimationTimer: see https://stackoverflow.com/a/60685975/2189127
You should also note that your FPS keyframe/timeline is not really measuring frames per second, in the sense of how frequently the scene graph is repainted. It is only measuring how frequently the width of the rectangle is updated (how often the property value is changed).

Java Swing - ScheduledExecutor Service Stops working upon UserInput

I am building a Maze where Monster is tracking the player and moving towards it. I have a function called SwapCells where I believe the problem lays in. SwapCells is used to move both Monster and Player. I have a ScheduledExecutorService that runs Monstermover which uses SwapCells. The Monster starts moving when the game starts, now the problems is when I start moving the player, the ScheduledExecutorService stops working and I can only move the player. I believe the problem is that, unlike MoveMonster, the Keylistener is not part of ScheduledExecutorService, hence when I invoke KeyListener that in turn runs SwapCell, it makes the ScheduledExecutorService stop. Any advice? Please Help!
private final int CELLSIZE = 20;
private ArrayList<Level> levels = new ArrayList<Level>();
private Player player;
private Monster monster;
private DisplayInfo display;
private int xSize;
private int ySize;
private int currentLevel = 0;
private int direction = 0;
//private CountDownTimer timer = new CountDownTimer(15);
public Game(int xSize, int ySize) {
player = new Player(0,0);
monster = new Monster(0,1);
setXSize(xSize);
setYSize(ySize);
setLayout(new GridLayout(ySize, xSize));
addLevel();
addKeyListener(new KeyListener() {
// POSSIBLE PROBLEM IS HERE !!
#Override
public void keyPressed(KeyEvent event) {
if(!hitWall) }
swapCells(getCurrentLevel().getMap()[x+dx][y+dy], getCurrentLevel().getMap()[x][y], player);
}
}
}
});
ScheduledExecutorService executor = Executors.newScheduledThreadPool(3);
executor.scheduleAtFixedRate(() -> moveMonster(), 0L, 1000L, TimeUnit.MILLISECONDS);
}
public void swapCells(Cell cell1, Cell cell2, Mover mover) {
Icon tempIcon = cell1.getLabel().getIcon();
String tempID = cell1.getID();
// sets mover's x and y value
mover.setX(cell1.getX());
mover.setY(cell1.getY());
// sets mover's cell to new cell
getCurrentLevel().getpMaze().setCell(cell1, mover);
getCurrentLevel().getMap()[cell1.getX()][cell1.getY()].setID(cell2.getID());
getCurrentLevel().getMap()[cell1.getX()][cell1.getY()].getLabel().setIcon(cell2.getLabel().getIcon());
getCurrentLevel().getMap()[cell2.getX()][cell2.getY()].setID(tempID);
getCurrentLevel().getMap()[cell2.getX()][cell2.getY()].getLabel().setIcon(tempIcon);
}
public void moveMonster() {
PerfectMaze currentMaze = getCurrentLevel().getpMaze();
ArrayList<Cell> trackToPlayer = currentMaze.getTrack(currentMaze.getMonsterCell(), currentMaze.getPlayerCell(), "P");
Collections.reverse(trackToPlayer);
Cell nextCell = trackToPlayer.get(1);
//swapCells(nextCell, currentMaze.getMonsterCell(), monster);
swapCells(getCurrentLevel().getMap()[nextCell.getX()][nextCell.getY()], getCurrentLevel().getMap()[monster.getX()][monster.getY()], monster);
}
You should only interact with Swing components from the Swing "Event Dispatch Thread" (EDT). But you created an ExecutorService, and its threads are manipulating Swing components directly.
Instead, perform your background work in a SwingWorker, which can pass graphical updates safely to the EDT.

Java Timer and drawing animations

I have no idea how to fix this problem. What I am doing is trying to break up a monster class I have into different things, like one class for player, one for fireball, etc. I had everything working before I tried to break up the class but now I am getting an error. I was wondering if anyone could help me solve it and explain to me how not to repeat this error again. Thank you in advance.
EDIT: The error is on: animationTimer = new Timer(animationDelay, this);
EDIT:1 error found:
File: C:\Users\jozef\Java\Dragon Ball Z\Player.java [line: 45]
Error: incompatible types: Player cannot be converted to java.awt.event.ActionListener
Also, I do format properly but when i try to copy and paste my code into the box to post here it doesn't count it as code so i have to indent every line to get it to appear as code and no normal text.
import java.awt.Graphics;
import java.awt.MediaTracker;
import javax.swing.ImageIcon;
import java.awt.Image;
import java.awt.event.ActionEvent;
import javax.swing.Timer;
public class Player {
int x;
int y;
ImageIcon pictures[];
int total;
int current;
boolean sideMove;
int move;
Timer animationTimer;
int animationDelay = 80;
public Player(int startX, int startY, ImageIcon image[], boolean sideMove, int move) {
x = startX;
y = startY;
pictures = image;
total = pictures.length;
this.sideMove = sideMove;
this.move = move;
startAnimation();
}
public void draw(Graphics g) {
if (pictures[current].getImageLoadStatus() == MediaTracker.COMPLETE) {
Image img = pictures[current].getImage();
g.drawImage(img, x, y, null);
current = (current + 1) % total;
}
update();
}
public void update() {
if (sideMove == true) {
x += move;
} else {
y += move;
}
}
public void startAnimation() {
if (animationTimer == null) {
current = 0;
animationTimer = new Timer(animationDelay, this); // *** error ***
animationTimer.start();
} else if (!animationTimer.isRunning())
animationTimer.restart();
}
public void stopAnimation() {
animationTimer.stop();
}
}
Here:
animationTimer = new Timer(animationDelay, this);
Since the Player class does not implement ActionListener this can not be passed in to the Timer constructor as a valid parameter. A possible solution is to have your Player class implement ActionListener giving it an appropriate actionPerformed method:
public class Player implements ActionListener {
#Override
protected void actionPerformed(ActionEvent e) {
// your coded here
}
// .... rest of your code
or better still, use a different ActionListener such as an anonymous inner class.
e.g.,
public void startAnimation() {
if (animationTimer == null) {
current = 0;
animationTimer = new Timer(animationDelay, e -> timerActionPerformed(e));
animationTimer.start();
} else if (!animationTimer.isRunning()) {
animationTimer.restart();
}
}
private void timerActionPerformed(ActionEvent e) {
// TODO repeated code goes here
}
Side recommendations:
You've got code within your painting method that changes your Player object's state, something that you'll want to avoid. Understand that you can only partially control when or even if an object will be painted, and so it would be best to keep these separate.
Myself, I'd get the Timer out of the Player class and instead use a Timer as your game loop or animation controller in a more general overall control class, perhaps the Game class (or whatever your "universe" class is called), the class that holds and controls all of your logical entities such as your Player objects.
This is the signature of javax.swing.Timer constructor:
public Timer(int delay, ActionListener listener)
You are providing an int and a Player..
You should create an ActionListener as well and provide it to the constructor or you could pass this but Player class should implements ActionListener inteface (you should write actionPerformed method in the Player class).
Read more info about Timer Here (official java doc).

Java slideshow image delay using paintComponent

I am putting together a slideshow program that will measure a user's time spent on each slide. The slideshow goes through several different magic tricks. Each trick is shown twice. Interim images are shown between the repetition. Transition images are shown between each trick.
On the first repetition of a trick the JPanel color flashes on the screen after a click before the next image is shown. This doesn't happen during the second repetition of the same trick. It's possible that the image is taking too long to load.
Is there an easy way to pre-load the images so that there isn't a delay the first time through?
NOTE: Original code deleted.
EDIT 1/10/2013: This code now works on slower machines. trashgod's second addendum helped the most. The mouseClick control structure periodically asks SwingWorker classes to load 40 images or less of the current trick while also setting the used images to null. I have simplified my code down for this to just two Image[]s and added a main method so it stands alone. Images are still required to run though. This is now pretty bare bones code, and if you're trying to make a slideshow with a lot of images I think it would be a good place to start.
NOTE: I think I figured out how to properly implement SwingWorker while still using multiple Image[]s. trashgod and kleopatra is this implementation in-line with what you were suggesting? I didn't end up using publish and process since I couldn't figure out how to get that to work appropriately with an indexed array, but because the StringWorker doesn't load all images in the array (only 40), and the code calls StringWorker every 20 images, there should be a pretty good buffer.
EDIT 1/10/2013 Changed out MouseListener by instead extending MouseAdapter on my Mouse class. Also fixed my paintComponent method to include a call to super.paintComponent(g).
Added publish/process methods to my SwingWorker class ImageWorker. Added a wrapper class, ArrayWrapper to allow passing imageArray[i] and its corresponding index int i with publish to process.
package slideshow3;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.MouseEvent;
import java.awt.event.MouseAdapter;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import java.util.List;
public class SlideShow3 extends JFrame
{
//screenImage will be replaced with each new slide
private Image screenImage;
private int width;
private int height;
//Create panel for displaying images using paintComponent()
private SlideShow3.PaintPanel mainImagePanel;
//Used for keybinding
private Action escapeAction;
//Image array variables for each trick
private Image[] handCuffs; //h
private Image[] cups; //c
//Used to step through the trick arrays one image at a time
private int h = 0;
private int c = 0;
//Used by timeStamp() for documenting time per slide
private long time0 = 0;
private long time1;
public SlideShow3()
{
super();
//Create instance of each Image array
handCuffs = new Image[50];
cups = new Image[176];
//start(handCuffsString);
start("handCuffs");
try
{
screenImage = ImageIO.read(new File("images/begin1.jpg"));
}
catch (IOException nm)
{
System.out.println("begin");
System.out.println(nm.getMessage());
System.exit(0);
}
/******************************************
* Removes window framing. The next line sets fullscreen mode.
* Once fullscreen is set width and height are determined for the window
******************************************/
this.setUndecorated(true);
GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().setFullScreenWindow(this);
width = this.getWidth();
height = this.getHeight();
//Mouse click binding to slide advance control structure
addMouseListener(new Mouse());
//Create panel so that I can use key binding which requires JComponent
mainImagePanel = new PaintPanel();
add(mainImagePanel);
/******************************************
* Key Binding
* ESC will exit the slideshow
******************************************/
// Key bound AbstractAction items
escapeAction = new EscapeAction();
// Gets the mainImagePanel InputMap and pairs the key to the action
mainImagePanel.getInputMap().put(KeyStroke.getKeyStroke("ESCAPE"), "doEscapeAction");
// This line pairs the AbstractAction enterAction to the action "doEnterAction"
mainImagePanel.getActionMap().put("doEscapeAction", escapeAction);
/******************************************
* End Key Binding
******************************************/
}
public static void main(String[] args)
{
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run()
{
SlideShow3 show = new SlideShow3();
show.setVisible(true);
}
});
}
//This method executes a specific SwingWorker class to preload images
public void start(String e)
{
if(e.equals("handCuffs"))
{
new ImageWorker(handCuffs.length, h, e).execute();
}
else if(e.equals("cups"))
{
new ImageWorker(cups.length, c, e).execute();
}
}
//Stretches and displays images in fullscreen window
private class PaintPanel extends JPanel
{
#Override
public void paintComponent(Graphics g)
{
if(screenImage != null)
{
super.paintComponent(g);
g.drawImage(screenImage, 0, 0, width, height, this);
}
}
}
/******************************************
* The following SwingWorker class Pre-loads all necessary images.
******************************************/
private class ArrayWrapper
{
private int i;
private Image image;
public ArrayWrapper(Image image, int i)
{
this.i = i;
this.image = image;
}
public int getIndex()
{
return i;
}
public Image getImage()
{
return image;
}
}
private class ImageWorker extends SwingWorker<Image[], ArrayWrapper>
{
private int currentPosition;
private int arraySize;
private String trickName;
private Image[] imageArray;
public ImageWorker(int arraySize, int currentPosition, String trick)
{
super();
this.currentPosition = currentPosition;
this.arraySize = arraySize;
this.trickName = trick;
}
#Override
public Image[] doInBackground()
{
imageArray = new Image[arraySize];
for(int i = currentPosition; i < currentPosition+40 && i < arraySize; i++)
{
try
{
imageArray[i] = ImageIO.read(new File("images/" + trickName + (i+1) + ".jpg"));
ArrayWrapper wrapArray = new ArrayWrapper(imageArray[i], i);
publish(wrapArray);
}
catch (IOException e)
{
System.out.println(trickName);
System.out.println(e.getMessage());
System.exit(0);
}
}
return imageArray;
}
#Override
public void process(List<ArrayWrapper> chunks)
{
for(ArrayWrapper element: chunks)
{
if(trickName.equals("handCuffs"))
{
handCuffs[element.getIndex()] = element.getImage();
}
else if(trickName.equals("cups"))
{
cups[element.getIndex()] = element.getImage();
}
}
}
#Override
public void done()
{
try
{
if(trickName.equals("handCuffs"))
{
handCuffs = get();
}
else if(trickName.equals("cups"))
{
cups = get();
}
}
catch(InterruptedException ignore){}
catch(java.util.concurrent.ExecutionException e)
{
String why = null;
Throwable cause = e.getCause();
if(cause != null)
{
why = cause.getMessage();
}
else
{
why = e.getMessage();
}
System.err.println("Error retrieving file: " + why);
}
}
}
/******************************************
* End SwingWorker Pre-Loading Classes
******************************************/
//Prints out time spent on each slide
public void timeStamp()
{
time1 = System.currentTimeMillis();
if(time0 != 0)
{
System.out.println(time1 - time0);
}
time0 = System.currentTimeMillis();
}
/******************************************
* User Input Classes for Key Binding Actions and Mouse Click Actions
******************************************/
private class EscapeAction extends AbstractAction
{
#Override
public void actionPerformed(ActionEvent e)
{
System.exit(0);
}
}
public class Mouse extends MouseAdapter
{
#Override
public void mouseClicked(MouseEvent e)
{
if(!(h<handCuffs.length) && !(c<cups.length))
{
timeStamp();
System.exit(0);
}
else if(h<handCuffs.length)
{
timeStamp();
screenImage = handCuffs[h];
repaint();
System.out.print("handCuffs[" + (h+1) + "]\t");
h++;
//purge used slides and refresh slide buffer
if(h == 20 || h == 40)
{
for(int i = 0; i < h; i++)
{
handCuffs[i] = null;
}
start("handCuffs");
}
if(h == 45)
{
start("cups");
}
}
else if(c<cups.length)
{
timeStamp();
screenImage = cups[c];
repaint();
System.out.print("cups[" + (c+1) + "]\t");
c++;
//purge used slides and refresh slide buffer
if(c == 20 || c == 40 || c == 60 || c == 80 || c == 100 || c == 120 || c == 140 || c == 160)
{
for(int i = 0; i < c; i++)
{
cups[i] = null;
}
start("cups");
}
}
}
}
/******************************************
* End User Input Classes for Key Binding Actions and Mouse Click Actions
******************************************/
}
This example uses a List<ImageIcon> as a cache of images returned by getImage(). Using getResource(), the delay is imperceptible. The next and previous buttons are bound to the Space key by default.
Addendum: You can control navigation by conditioning a button's setEnabled() state using an instance of javax.swing.Timer, for example.
Addendum: Your second example waits until the mouse is clicked to begin reading an image, an indeterminate process that may return a copy immediately or may not complete until after repaint(). Instead, begin reading the images in the background using ImageIO.read(), as shown here. You can process() your List<Image> and show progress, as seen here. The SwingWorker can be launched from the initial thread, running while you subsequently build your GUI on the EDT. You can display the first image as soon as it is processed.

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