原文:http://wiki.lwjgl.org/wiki/LWJGL_Basics_4_(Timing)
Timers 计时器
精确计时对于高性能游戏很重要。一般计时器的精度至少得是1毫秒。
Java的System.currentTimeMillis()方法可以根据运行的操作系统具备1至55毫秒之间某个精度,这将导致游戏跨平台不太容易。
System.nanoTime()
Java 1.5引入了此方法,可以提供毫秒级的精度。用System.nanoTime()取毫秒的话,必须将返回值除以1,000,000(纳秒→毫秒)
/**
* Get the time in milliseconds
*
* @return The system time in milliseconds
*/
public long getTime() {
return System.nanoTime() / 1000000;
}
Sys.getTime()
LWJGL里的Sys.getTime()方法是一个高度的解决方案。它将返回以“滴达”单位返回时间。这值可以转成任何的格式(纳秒、微秒、毫秒等等)以合理适应它的平台。Sys.getTimerResolution()方法将返回一秒内的滴达数。此两个方法使用时都可以转换为任何需要的单位(包括毫秒)。详见Javadoc里的LWJGL Sys类。
为了从Sys.getTime()取得毫秒数,滴达数要乘以1000,然后再除以每秒的滴达数。
/**
* Get the time in milliseconds
*
* @return The system time in milliseconds
*/
public long getTime() {
return (Sys.getTime() * 1000) / Sys.getTimerResolution();
}
以上任何一个计时器都是可以满足需求的。
Delta Time 时刻差
在许多游戏里,有一个叫时刻差的整数值经常被用来保存上一帧到本帧经过的毫秒数。此值可以用来在游戏中以帧独立的方式来移动实体们,也就是说,不需要刻意去关注fps是快了还是慢了,所有的东西都会以固定的速度移动的。
想计算这值,需要保存每帧的时刻值,然后用本帧的减去上一帧的。
public int getDelta() {
long time = getTime();
int delta = (int) (time - lastFrame);
lastFrame = time;
return delta;
}
Sleeping and Display.sync() 休眠和Display.sync()
休眠对游戏来说很重要,不然游戏循环将会能跑多快跑多快,占尽系统资源并且热爆CPU、GPU,耗电超快。休眠使程序可以适当休息,以免用尽系统全部资源。有许多种方式可用,比如Thread.sleep()或者Thread.yield()。然后LWJGL提供了更简便的Display.sync(int fps)方法。此方法允许游戏循环以特定的帧率运行(如60fps)并且可以在任何额外的时间里休眠。这方法必须在每帧里调用(可以放在游戏循环内)。另一个实现的方法是使用Vsync,这个之后的教程会再讲。
Calculating FPS 计算FPS
每秒帧数(Frame Per Second,即FPS)可以通过每帧累加一个整数值来计算,几秒过去,将此值显示给用户(然后在下帧就就归零并重新计算)。为了简便,我们用Display.setTitle(String 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 例子
以下是一个完整例子,它使用时刻差旋转或移动一个方形,用方向键可以移动方形。
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();
}
}