LWJGL Basics 4 (Timing)

From LWJGL
Jump to: navigation, search

Contents

Timers

Accurate timing is important for any high performance application or game. Typically a timer with an accuracy of at least 1 millisecond is needed.

Java's System.currentTimeMillis() has an accuracy of anywhere between 1 to 55 milliseconds depending on the operating system its run on. This makes it unsuitable for cross-platform games.

System.nanoTime()

This was introduced in Java 1.5 and is able to produce millisecond accuracy. To get milliseconds from System.nanoTime() the value just needs to be divided by 1,000,000 (nanoseconds -> milliseconds).

/**
 * Get the time in milliseconds
 * 
 * @return The system time in milliseconds
 */
public long getTime() {
    return System.nanoTime() / 1000000;
}

Sys.getTime()

The Sys.getTime() method is LWJGL's high resolution timer and will return time in 'ticks'. This value can be in any format (nanoseconds, microsecond, milliseconds, etc) whichever is deemed to be best on the platform. The Sys.getTimerResolution() method will return the number of 'ticks' in a second. When both these methods are used together it allows conversion to any required metric (including milliseconds). See the Javadoc for LWJGL Sys class for more information.

To get milliseconds from Sys.getTime(), the 'ticks' are multiplied by 1000 so that the end result will be in milliseconds and then divide by the number of ticks in a second.

/**
 * Get the time in milliseconds
 * 
 * @return The system time in milliseconds
 */
public long getTime() {
    return (Sys.getTime() * 1000) / Sys.getTimerResolution();
}

Either of the timers above should be sufficient.


Delta Time

In many games an int value (usually called delta) is used to store the number of milliseconds that have passed since the last frame. This value can be used to move entities in the game in a frame independent way. Meaning that regardless of how fast or slow the fps is everything should move at a constant speed.

This value is calculated by saving the time from the timer every frame and subtracting it from the time on the previous frame.

public int getDelta() {
    long time = getTime();
    int delta = (int) (time - lastFrame);
    lastFrame = time;
    	
    return delta;
}


Sleeping and Display.sync()

Sleeping is very important in games and is needed otherwise the game loop will run as fast as possible, taking up all the system resources and in most cases heating CPU's, GPU's and draining batteries. Sleeping will allow the application to sleep and thus not use all the resources on the computer. There are various loops that can be created using options like Thread.sleep() and Thread.yield(). However LWJGL provides the method Display.sync(int fps) to make this easier. This method allows the game loop to run at the specified frame rate (e.g 60fps) and sleep for any extra time. This method must be called every frame (i.e. put it in the game loop). Vsync is another option that can be used to achieve this but that will be covered in a later tutorial.


Calculating FPS

Frame Per Second can be calculated by incrementing an int variable every frame and after a second has passed showing this value to the user (the value is reset to 0 thereafter for next frame). For simplicity we will use the Display.setTitle(String title) method to display the frame rate in the window title.


public void start() {
    //some startup code
    lastFPS = getTime(); //set lastFPS to current Time
}


/**
 * Calculate the FPS and set it in the title bar
 */
public void updateFPS() {
    if (getTime() - lastFPS > 1000) {
        Display.setTitle("FPS: " + fps); 
        fps = 0; //reset the FPS counter
        lastFPS += 1000; //add one second
    }
    fps++;
}

Example

The full example below will use the delta value to rotate and move a quad. Use the cursor keys to move the quad around.

import org.lwjgl.LWJGLException;
import org.lwjgl.Sys;
import org.lwjgl.input.Keyboard;
import org.lwjgl.opengl.Display;
import org.lwjgl.opengl.DisplayMode;
import org.lwjgl.opengl.GL11;

public class TimerExample {

	/** position of quad */
	float x = 400, y = 300;
	/** angle of quad rotation */
	float rotation = 0;
	
	/** time at last frame */
	long lastFrame;
	
	/** frames per second */
	int fps;
	/** last fps time */
	long lastFPS;

	public void start() {
		try {
			Display.setDisplayMode(new DisplayMode(800, 600));
			Display.create();
		} catch (LWJGLException e) {
			e.printStackTrace();
			System.exit(0);
		}

		initGL(); // init OpenGL
		getDelta(); // call once before loop to initialise lastFrame
		lastFPS = getTime(); // call before loop to initialise fps timer

		while (!Display.isCloseRequested()) {
			int delta = getDelta();
			
			update(delta);
			renderGL();

			Display.update();
			Display.sync(60); // cap fps to 60fps
		}

		Display.destroy();
	}
	
	public void update(int delta) {
		// rotate quad
		rotation += 0.15f * delta;
		
		if (Keyboard.isKeyDown(Keyboard.KEY_LEFT)) x -= 0.35f * delta;
		if (Keyboard.isKeyDown(Keyboard.KEY_RIGHT)) x += 0.35f * delta;
		
		if (Keyboard.isKeyDown(Keyboard.KEY_UP)) y -= 0.35f * delta;
		if (Keyboard.isKeyDown(Keyboard.KEY_DOWN)) y += 0.35f * delta;
		
		// keep quad on the screen
		if (x < 0) x = 0;
		if (x > 800) x = 800;
		if (y < 0) y = 0;
		if (y > 600) y = 600;
		
		updateFPS(); // update FPS Counter
	}
	
	/** 
	 * Calculate how many milliseconds have passed 
	 * since last frame.
	 * 
	 * @return milliseconds passed since last frame 
	 */
	public int getDelta() {
	    long time = getTime();
	    int delta = (int) (time - lastFrame);
	    lastFrame = time;
	 
	    return delta;
	}
	
	/**
	 * Get the accurate system time
	 * 
	 * @return The system time in milliseconds
	 */
	public long getTime() {
	    return (Sys.getTime() * 1000) / Sys.getTimerResolution();
	}
	
	/**
	 * Calculate the FPS and set it in the title bar
	 */
	public void updateFPS() {
		if (getTime() - lastFPS > 1000) {
			Display.setTitle("FPS: " + fps);
			fps = 0;
			lastFPS += 1000;
		}
		fps++;
	}
	
	public void initGL() {
		GL11.glMatrixMode(GL11.GL_PROJECTION);
		GL11.glLoadIdentity();
		GL11.glOrtho(0, 800, 0, 600, 1, -1);
		GL11.glMatrixMode(GL11.GL_MODELVIEW);
	}

	public void renderGL() {
		// Clear The Screen And The Depth Buffer
		GL11.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT);

		// R,G,B,A Set The Color To Blue One Time Only
		GL11.glColor3f(0.5f, 0.5f, 1.0f);

		// draw quad
		GL11.glPushMatrix();
			GL11.glTranslatef(x, y, 0);
			GL11.glRotatef(rotation, 0f, 0f, 1f);
			GL11.glTranslatef(-x, -y, 0);
			
			GL11.glBegin(GL11.GL_QUADS);
				GL11.glVertex2f(x - 50, y - 50);
				GL11.glVertex2f(x + 50, y - 50);
				GL11.glVertex2f(x + 50, y + 50);
				GL11.glVertex2f(x - 50, y + 50);
			GL11.glEnd();
		GL11.glPopMatrix();
	}
	
	public static void main(String[] argv) {
		TimerExample timerExample = new TimerExample();
		timerExample.start();
	}
}


Credit

Tutorial Credit - Ninja Cave

Personal tools
Namespaces

Variants
Actions
Navigation
Tools