这几天一直在看 <<Killer Game Programming in Java>>,非常经典的好书,现在对游戏有了一个基本的认识,过几天就写个贪吃蛇出来.
因为看得有点快,真正准备写代码的时候又发现自己对一些基本知识点还是比较模糊,又返回去看前面.边看边做笔记,感觉确实理解得要更加清楚了.
*FPS 和 按时间准确的Sleeping*
============================
FPS
--------
一个测量animation速度的常用指标就是桢速(每秒显示桢的数量:frames per second),简称FPS.在下面的
代码中,做一次gameUpdate和gameRender的循环就对应一个桢.
比如100FPS表示run()中的每次迭代应该用1000/100 == 10ms.这个迭代时间存在period变量中.
//code example:
public void run() {
long beforeTime, timeDiff, sleepTime;
beforeTime = System.currentTimeMillis();
running = true;
while(running) {
gameUpdate();//计算game中的model
gameRender();//画一个image, "double buffer"
paintScreen();//在screen上显示image
timeDiff = System.currentTimeMillis() - beforeTime;
sleepTime = period - timeDiff;//计算需要sleep的时间
if (sleepTime <= 0)
sleepTime = 5;
try {
Thread.sleep(sleepTime);
}catch(InterruptedException e);
beforeTime = System.currentTimeMillis();
}
}
--------
Timer Resolution
连续调用两次timer中间必需的最小时间.这样才能保证每次调用返回不同的时间.
比如:
long t1 = System.currentTimeMillis( );
long t2 = System.currentTimeMillis( );
long diff = t2 - t1; // 实际输出是0,单位是ms
在win95和win98上,resolution值是55ms,说明只有在每隔55ms后调用timer才会返回不同的值.
在animation loop中,resolution的会导致animation比期望的要慢而且减小了FPS.因为如果gameUpdate
和gameRender的时间小于55ms,那么timeDiff变量就会设为0,那么sleepTime就会比实际需要的时间要大.
为了防止这个问题,每个循环周期时间必须大于55ms,表示最高限制是大约18FPS.这个frame rate被广泛接
受,因为屏幕刷新过慢会表现得象闪屏(excessive flicker)一样.
在Windows2000, NT和XP上, currentTimeMillis()的resolution是10到15ms,这样就可以获得67-100FPS.
这个值对游戏来说是可以接受的.在Mac OS X和Linux上的resolution是1ms,相当好了.
--------
改进过的J2SE Timers
J2SE 1.4.2有一个没有被写入到文档的精确到微秒的timer class: sum.misc.Perf.
Pref计算diff的方法:
Pref perf = Perf.getPerf();
long countFrep = perf.highResFrequency();
long count1 = perf.highResCounter();
long count2 = perf.highResCounter();
long diff = (count2 - count1) * 1000000000L / countFreq;
//转换成纳秒nanoseconds
nanoseconds:十忆分之1秒
J2SE 5.0中解决了这个timer的问题,System.nanoTime(),可以象Pref timer一样来计算时间.
long count1 = System.nanoTime();
long count2 = System.nanoTime();
long diff = (count1 - count2);//单位是纳秒
--------
Non-J2SE Timers
Java 3D timer的计算方法:
long t1 = J3DTimer.getValue();
long t2 = j3DTimer.getValue();
long diff = t2 - t1;//单位是纳秒
*更好的Sleeping*
================
animation循环依赖一个好的timer和精确的sleep方法调用.现在在前面的基础上改进代码,以保证需要
的桢速.
//code example:
private static final int NO_DELAYS_PER_YIELD = 16;
public void run() {
long beforeTime, afterTime, timeDiff, sleepTime;
long overSleepTime = 0L;
int noDelays = 0;
beforeTime = J3DTimer.getValue();
running = true;
while(running) {
gameUpdate();
gameRender();
paintScreen();
afterTime = J3DTimer.getValue();
timeDiff = afterTime - beforeTime;
sleepTime = (period - timeDiff) - overSleepTime;
if (sleepTime > 0) {
try{
Thread.sleep(sleepTime/1000000L); //nano -> ms
}catch(InterrruptedException ex){}
overSleepTime = (J3DTimer.getValue() - afterTime) - sleepTime;
}else { //sleepTime <= 0; 桢的时间大于期望的period,
//不sleep直到sleepTime > 0 或 连续运行了NO_DELAYS_PER_YIELD次
overSleepTime = 0L;
if (++noDelays >= NO_DELAYS_PER_YIELD) {
Thread.yield();
noDelays = 0;
}
}
beforeTime = J3DTimer.getValue();
}
}
如果sleep()设置成sleep 10 ms,但是确用了12 ms,那么overSleepTime会被设置成2 ms,下次就会少
sleep 2ms.
如果桢的时间大于期望的period,那么就不浪费时间sleep,而是一直循环,一定次数后调用Thread.yield(),
这样来节省时间而又保证其它线程有机会运行.
*FPS和UPS*
==========
除了FPS,还有一个有用的测量animation速度的指标:UPS. 在现在的animation循环中每次迭代拥有一次
update和render.但是这个对应不是必需的.在循环中,可以每一次render前做两次updates.
//code example:
public void run() {
...
running = true;
while(running) {
gameUpdate();//update 游戏状态
gameUpdate();//再一次update 游戏状态
gameRender();
paintScreen();
//sleep
}
System.exit(0);
}
在上面的代码中,如果游戏提供了50FPS,那么就每秒就会做100次updates.
从Rendering中分离Updates
--------
对于高FPS速率的一个限制是update和render所需要的时间.假设period = 5ms(1000/5 == 200FPS),如果
update和render需要的时间大于5ms,那么200FPS就不可能达到.而它们所需要的时间中大部分是被render所消耗的.
在这种情况下,增加游戏速度的方法是增加UPS的速率.在编程中,也就是在每次迭代中增加gameUpdate的次数.
但是注意,如果增加gameUpdate的次数过多的话会造成游戏不连续,因为有许多游戏状态没有显示出来.
新的代码:
//code example:
private static int MAX_FRAME_SKIPS = 5;
public void run() {
long beforeTime, afterTime, timeDiff, sleepTime;
long overSleepTime = 0L;
int noDelays = 0;
long excess = 0L;
beforeTime = J3DTimer.getValue();
running = true;
while(running) {
gameUpdate();
gameRender();
paintScreen();
afterTime = J3DTimer.getValue();
timeDiff = afterTime - beforeTime;
sleepTime = (period - timeDiff) - overSleepTime;
if (sleepTime > 0) {
try{
Thread.sleep(sleepTime/1000000L); //nano -> ms
}catch(InterrruptedException ex){}
overSleepTime = (J3DTimer.getValue() - afterTime) - sleepTime;
}else {
excess -= sleepTime;
overSleepTime = 0L;
if (++noDelays >= NO_DELAYS_PER_YIELD) {
Thread.yield();
noDelays = 0;
}
}
beforeTime = J3DTimer.getValue();
int skips = 0;
while((excess > period) && (skips < MAX_FRAME_SKIPS)) {
excess -= period;
gameUpdate();
skips++;
}
}
}
如果update/render实际需要12ms,但是需要的period是10ms,那么sleepTime会是-2ms(由于引入overSleepTime,
所以可能会更小一点).额外的执行时间被加到excess变量中.
当excess达到period大小时,那么相当于丢失了一个桢,在while循环中,为每次丢失执行gameUpdate.但是限制在
MAX_FRAME_SKIPS里.
这样做的优点是,如果一个游戏的update/render速度不能满足期望的FPS时,那么就会另外执行gameUpdate.
这样改变了游戏的状态但是没有马上显示出来,最后用户会看见游戏移动更"快"了,虽然每秒钟显示的桢数并没有
改变.
本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/starshus/archive/2006/11/03/1364979.aspx