I am trying to create a little game for a project in university. Since I need to update the screen permanently I am looking for a good render loop implementation. It should be full screen. I found this on gamedev.net – Java Games: Active Rendering:
import java.awt.*; import java.awt.event.KeyAdapter; import java.awt.event.KeyEvent; import java.awt.image.BufferStrategy; import java.awt.image.BufferedImage; import java.util.Random; import javax.swing.JFrame; public class SimpleFullScreenGame { static boolean running; public static void main( String[] args ) { // Create game window... JFrame app = new JFrame(); app.setIgnoreRepaint( true ); app.setUndecorated( true ); // Add ESC listener to quit... app.addKeyListener( new KeyAdapter() { public void keyPressed( KeyEvent e ) { if( e.getKeyCode() == KeyEvent.VK_ESCAPE ) running = false; } }); // Get graphics configuration... GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment(); GraphicsDevice gd = ge.getDefaultScreenDevice(); GraphicsConfiguration gc = gd.getDefaultConfiguration(); // Change to full screen gd.setFullScreenWindow( app ); if( gd.isDisplayChangeSupported() ) { gd.setDisplayMode( new DisplayMode( 640, 480, 32, DisplayMode.REFRESH_RATE_UNKNOWN ) ); } // Create BackBuffer... app.createBufferStrategy( 2 ); BufferStrategy buffer = app.getBufferStrategy(); // Create off-screen drawing surface BufferedImage bi = gc.createCompatibleImage( 640, 480 ); // Objects needed for rendering... Graphics graphics = null; Graphics2D g2d = null; Color background = Color.BLACK; Random rand = new Random(); // Variables for counting frames per seconds int fps = 0; int frames = 0; long totalTime = 0; long curTime = System.currentTimeMillis(); long lastTime = curTime; running = true; while( running ) { try { // count Frames per second... lastTime = curTime; curTime = System.currentTimeMillis(); totalTime += curTime - lastTime; if( totalTime > 1000 ) { totalTime -= 1000; fps = frames; frames = 0; } ++frames; // clear back buffer... g2d = bi.createGraphics(); g2d.setColor( background ); g2d.fillRect( 0, 0, 639, 479 ); // draw some rectangles... for( int i = 0; i < 20; ++i ) { int r = rand.nextInt(256); int g = rand.nextInt(256); int b = rand.nextInt(256); g2d.setColor( new Color(r,g,b) ); int x = rand.nextInt( 640/2 ); int y = rand.nextInt( 480/2 ); int w = rand.nextInt( 640/2 ); int h = rand.nextInt( 480/2 ); g2d.fillRect( x, y, w, h ); } // display frames per second... g2d.setFont( new Font( "Courier New", Font.PLAIN, 12 ) ); g2d.setColor( Color.GREEN ); g2d.drawString( String.format( "FPS: %s", fps ), 20, 20 ); // Blit image and flip... graphics = buffer.getDrawGraphics(); graphics.drawImage( bi, 0, 0, null ); if( !buffer.contentsLost() ) buffer.show(); } finally { // release resources if( graphics != null ) graphics.dispose(); if( g2d != null ) g2d.dispose(); } } gd.setFullScreenWindow( null ); System.exit(0); } }
I executed the code and there are two things I am wondering about:
My display resolution is 1920×1080. The code is using 640×480. Therefore the rendered rectangles look like a 640×480 picture upscaled to 1920×1080 meaning you have huge pixels and the code doesn’t take avantage of the higher posible resolution of the display. How can I adapt the code to actually use the native display resolution of the user?
Second, usually what you see when you take a look on games code is a rendering loop living in its own thread to not block the main thread. However thats not the case here. Why? I assume because the JFrame already lives in its own thread created by swing?
Thanks for the help.
Advertisement
Answer
There are lots of issues with this code.
First of all, when you are checking (polling) a flag variable in the main thread, that will be updated by a key listener, which will be called in the event dispatch thread, the minimum you have to do, is to declare that variable volatile
.
Then, there is no point in using JFrame
when you don’t use the Swing framework at all. Further, it’s nonsensical to request double buffering from the AWT and then, use a BufferedImage
for another buffering atop the already buffered operation.
Using the native resolution is as easy as removing the setDisplayMode(…)
call. After turning the window to full screen, you can simply use getWidth()
and getHeight()
on it to get the actual dimensions for the operations (it’s not needed for the buffered image, as that was obsolete anyway).
public class SimpleFullScreenGame { static volatile boolean running = true; public static void main(String[] args) { // Create game window... Frame app = new Frame(); app.setIgnoreRepaint(true); app.setUndecorated(true); // Add ESC listener to quit... app.addKeyListener(new KeyAdapter() { public void keyPressed(KeyEvent e) { if(e.getKeyCode() == KeyEvent.VK_ESCAPE) running = false; } }); GraphicsDevice dev = app.getGraphicsConfiguration().getDevice(); // Change to full screen dev.setFullScreenWindow(app); int width = app.getWidth(), height = app.getHeight(); // Create BackBuffer... app.createBufferStrategy(2); BufferStrategy buffer = app.getBufferStrategy(); // Objects needed for rendering... Color background = Color.BLACK, textColor = Color.GREEN; Font font = new Font("Courier New", Font.PLAIN, 12); ThreadLocalRandom rand = ThreadLocalRandom.current(); // Variables for counting frames per seconds int fps = 0, frames = 0, totalTime = 0; long currTime = System.nanoTime(), lastTime; while(running) { // count Frames per second... lastTime = currTime; currTime = System.nanoTime(); totalTime += currTime - lastTime; if(totalTime > 1_000_000_000) { totalTime -= 1_000_000_000; fps = frames; frames = 0; } frames++; Graphics gfx = buffer.getDrawGraphics(); gfx.setColor(background); gfx.fillRect(0, 0, width, height); // draw some rectangles... for(int i = 0; i < 20; ++i) { gfx.setColor(new Color(rand.nextInt(0x1000000))); int x = rand.nextInt(width/2), y = rand.nextInt(height/2); int w = rand.nextInt(width/2), h = rand.nextInt(height/2); gfx.fillRect(x, y, w, h); } // display frames per second... gfx.setFont(font); gfx.setColor(textColor); gfx.drawString("FPS: " + fps, 20, 20); gfx.dispose(); if(!buffer.contentsLost()) buffer.show(); } dev.setFullScreenWindow(null); System.exit(0); } }
I made some other small improvements.