Posted on 2006-11-06 18:45
luosheng 閱讀(380)
評論(0) 編輯 收藏
這幾天一直在看 <<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.
這樣改變了游戲的狀態但是沒有馬上顯示出來,最后用戶會看見游戲移動更"快"了,雖然每秒鐘顯示的楨數并沒有
改變.
from:http://blog.csdn.net/starshus/archive/2006/11/03/1364979.aspx
?