I tried to write a simple structure for a 2D game in java, using active rendering and fps independent game updates and I ran into a problem with my run() method.
As you can see in the code below, I create a BufferedImage frameBuff, draw a bunch of rectangles on it and render it to the JPanel panel in the nested Window class. My problem is, when I set my fps cap really low, I can see that the first BufferedImage frameBuff is not really drawn to the panel, eventhough the renderImage() method gets called. This seems to be due to the fact, that the Graphics2D object does not finish drawing before i render frameBuff to the panel.
Sure I could just sleep some time after drawing before I render the frameBuff, but the amount of time needed to finish drawing to the frameBuff might vary when the scene gets more complex. So is there a way to wait/sleep exactly until g2d has finished drawing?
import java.awt.*;
import java.awt.image.BufferedImage;
import javax.swing.*;
import javax.swing.border.EmptyBorder;
public class Game implements Runnable
{
private Window gui;
private int frameCap;
private double delta, fixedSpeed, frameTimeCap;
private long lastFrame, currentFrame;
private BufferedImage frameBuff;
private Thread mainThread;
private boolean running;
private static final long oneSecondInNano = 1000000000;
private static final long oneMilliInNano = 1000000;
public static void main(String[] args)
{
new Game(60, 0);
}
/**
* Sets up the Game, setting its fixed speed (time relative) and the
* framecap.
*
* @param fixedSpeed
* - the speed at which the game should run in units/second
* <p>
* <strong>Example:</strong> If a rectangle should move 60
* pixels/second, the speed would be set to 60 and the rectangle
* would be moved by 1*delta every frame)
* @param frameCap
* - the amount of frames/second the game must not exceed
*/
public Game(double fixedSpeed, int frameCap)
{
this.frameCap = frameCap;
if(this.frameCap == 0)
this.frameTimeCap = 0;
else
this.frameTimeCap = oneSecondInNano/frameCap;
this.fixedSpeed = oneSecondInNano/fixedSpeed;
gui = new Window();
running = true;
lastFrame = System.nanoTime();
mainThread = new Thread(this);
mainThread.start();
}
/**
* Mainloop
*/
@Override
public void run()
{
double x = 0;
while (running)
{
//Calculating delta
currentFrame = System.nanoTime();
long diff = currentFrame - lastFrame;
System.out.println("last Frametime:\t" + diff + "ns");
delta = diff / fixedSpeed;
//Update Game
if(frameBuff == null)
frameBuff = new BufferedImage(640, 480, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2d = (Graphics2D)frameBuff.getGraphics();
g2d.setColor(Color.red);
g2d.fillRect(0, 0, frameBuff.getWidth(), frameBuff.getHeight());
g2d.setColor(Color.black);
System.out.println("current Delta:\t" + delta);
x+= 3*delta;
g2d.fillRect((int)x%640, (int)x%480, 20, 20);
if(x%640 > 620 && x%480 > 460)
g2d.fillRect(0, 0, (int)x%640 - 620, (int)x%480 - 460);
else if(x%640 > 620)
g2d.fillRect(0, (int)x%480, (int)x%640 - 620, 20);
else if(x%480 > 460)
g2d.fillRect((int)x%640, 0, 20, (int)x%480 - 460);
g2d.dispose();
//Render image
gui.renderImage(frameBuff);
//Wait a bit
Thread.yield();
//Stay at/under the frameCap
diff = System.nanoTime() - currentFrame;
if(diff < frameTimeCap)
{
try
{
Thread.sleep((long)(frameTimeCap - diff)/oneMilliInNano);
}
catch(InterruptedException e)
{
e.printStackTrace();
}
}
lastFrame = currentFrame;
}
}
/**
* GUI for the Game.
*
*/
public class Window extends JFrame
{
private static final long serialVersionUID = 1L;
JPanel contentPane, panel;
/**
* Sets up the Window. Creates the JPanel.
*/
public Window()
{
// Sets up the contentPane with Border and LayoutManager
contentPane = new JPanel();
contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
contentPane.setLayout(new BoxLayout(contentPane, BoxLayout.X_AXIS));
this.setContentPane(contentPane);
this.setDefaultCloseOperation(EXIT_ON_CLOSE);
this.setResizable(false);
// Sets up the panel and adds it to the contentPane
panel = new JPanel();
//panel.setBackground(Color.cyan);
panel.setPreferredSize(new Dimension(640, 480));
contentPane.add(panel);
this.pack();
this.setVisible(true);
this.setIgnoreRepaint(true);
panel.setIgnoreRepaint(true);
}
/**
* Renders a BufferedImage using the JPanel's Graphics Object.
*
* @param im
* Buffered Image that is drawn.
*/
public void renderImage(BufferedImage im)
{
Graphics2D g2d = (Graphics2D) panel.getGraphics();
g2d.drawImage(im, 0, 0, null);
}
}
}