2011-03-23 36 views
0

我正在寫一個相當簡單的2D多人-過網絡遊戲。現在,我發現自己幾乎不可能創建一個穩定的循環。我認爲穩定的意思是這樣的循環,在這種循環中進行了一定的計算,並在嚴格的時間段內重複(例如,每25毫秒,這就是我現在正在爲之奮鬥的時間)。除此之外,我還沒有遇到過很多嚴重的障礙。試圖創造一個穩定的遊戲引擎循環

在這個遊戲中,多個線程運行,無論是在服務器和客戶端應用程序,分配給各種任務。我們以服務器應用程序中的引擎線程爲例。在這個主題中,我嘗試使用Thread.sleep創建遊戲循環,嘗試考慮遊戲計算所花費的時間。這裏是我的循環,放在run()方法中。 Tick()函數是循環的有效載荷。它只是包含對不斷更新遊戲的其他方法的有序調用。

long engFPS = 40; 
long frameDur = 1000/engFPS; 
long lastFrameTime; 
long nextFrame; 

< ...>

while(true) 
{ 
    lastFrameTime = System.currentTimeMillis(); 
    nextFrame = lastFrameTime + frameDur; 

    Tick(); 

    if(nextFrame - System.currentTimeMillis() > 0) 
    { 
     try 
     { 
      Thread.sleep(nextFrame - System.currentTimeMillis()); 
     } 
     catch(Exception e) 
     { 
      System.err.println("TSEngine :: run :: " + e); 
     } 
    } 
} 

的主要問題是,剛纔的Thread.sleep愛背叛你約多少會睡的預期。它可以很容易地把線程休息或更長的時間更短的時間,特別是在一些機器上的Windows XP(我測試過它自己,WinXP中給出真是可惡結果相比Win7和其他OS)。我在互聯網上搜索了很多,結果令人失望。這似乎是我們正在運行的操作系統的線程調度程序的錯誤,以及它的所謂粒度。據我瞭解,這個調度程序在一定的時間內不斷檢查系統中每個線程的需求,特別是把它們從睡眠中喚醒/喚醒。當重新檢查時間很短(如1ms)時,情況看起來很平穩。雖然,據說WinXP的粒度高達10或15毫秒。我也讀過,不僅是Java程序員,而且使用其他語言的人也面臨這個問題。知道這一點,製作穩定,堅固,可靠的遊戲引擎似乎幾乎是不可能的。儘管如此,它們無處不在。 我非常想知道用哪種方法可以解決這個問題。更有經驗的人能給我一個提示嗎?

+0

以最大FPS運行並根據實際幀時間而不是常數進行計算。 – Erik 2011-03-23 12:13:57

回答

2

不要依賴操作系統或任何計時器機制上喚醒你的線程或時間或在一個精確的點調用一些回調準確的延遲。這不會發生。

解決此問題的方法不是設置睡眠/回調/輪詢時間間隔,然後假定間隔保持高度精確度,跟蹤自上次迭代以來所經過的時間量並用它來確定當前狀態應該是什麼。通過通過這個有出息,更新基於當前「幀」(你真的應該設計自己的發動機的方式,內部部件不知道或關心什麼混凝土爲框架的狀態,所以,與其有剛狀態,通過時間流暢地移動,並且當需要發送新幀以呈現該狀態的快照時)。

因此,例如,你可以這樣做:

long maxWorkingTimePerFrame = 1000/FRAMES_PER_SECOND; //this is optional 
lastStartTime = System.currentTimeMillis(); 
while(true) 
{ 
    long elapsedTime = System.currentTimeMillis() - lastStartTime; 
    lastStartTime = System.currentTimeMillis(); 

    Tick(elapsedTime); 

    //enforcing a maximum framerate here is optional...you don't need to sleep the thread 
    long processingTimeForCurrentFrame = System.currentTimeMillis() - lastStartTime; 
    if(processingTimeForCurrentFrame < maxWorkingTimePerFrame) 
    { 
     try 
     { 
      Thread.sleep(maxWorkingTimePerFrame - processingTimeForCurrentFrame); 
     } 
     catch(Exception e) 
     { 
      System.err.println("TSEngine :: run :: " + e); 
     } 
    } 
} 

另外請注意,您可以通過代替System.currentTimeMillis()使用System.nanoTime()獲得更大的計時器粒度。

0

也許這可以幫助你。 從Java中 大衛brackeen的博克遊戲開發公司,並計算平均粒度假一更流暢的幀速率: link

public class TimeSmoothie { 
    /** 
     How often to recalc the frame rate 
    */ 
    protected static final long FRAME_RATE_RECALC_PERIOD = 500; 
    /** 
      Don't allow the elapsed time between frames to be more than 100 ms 

    */ 
    protected static final long MAX_ELAPSED_TIME = 100; 
    /** 

     Take the average of the last few samples during the last 100ms 

    */ 
    protected static final long AVERAGE_PERIOD = 100; 
    protected static final int NUM_SAMPLES_BITS = 6; // 64 samples 
    protected static final int NUM_SAMPLES = 1 << NUM_SAMPLES_BITS; 
    protected static final int NUM_SAMPLES_MASK = NUM_SAMPLES - 1; 
    protected long[] samples; 
    protected int numSamples = 0; 
    protected int firstIndex = 0; 
    // for calculating frame rate 
    protected int numFrames = 0; 
    protected long startTime; 
    protected float frameRate; 

    public TimeSmoothie() { 
     samples = new long[NUM_SAMPLES]; 
    } 
    /** 
     Adds the specified time sample and returns the average 
     of all the recorded time samples. 
    */ 

    public long getTime(long elapsedTime) { 
     addSample(elapsedTime); 
     return getAverage(); 
    } 

    /** 
     Adds a time sample. 
    */ 

    public void addSample(long elapsedTime) { 
     numFrames++; 
     // cap the time 
     elapsedTime = Math.min(elapsedTime, MAX_ELAPSED_TIME); 
     // add the sample to the list 
     samples[(firstIndex + numSamples) & NUM_SAMPLES_MASK] = 
      elapsedTime; 
     if (numSamples == samples.length) { 
      firstIndex = (firstIndex + 1) & NUM_SAMPLES_MASK; 
     } 
     else { 
      numSamples++; 
     } 
    } 
    /** 
     Gets the average of the recorded time samples. 
    */ 

    public long getAverage() { 
     long sum = 0; 
     for (int i=numSamples-1; i>=0; i--) { 
      sum+=samples[(firstIndex + i) & NUM_SAMPLES_MASK]; 
      // if the average period is already reached, go ahead and return 
      // the average. 
      if (sum >= AVERAGE_PERIOD) { 
       Math.round((double)sum/(numSamples-i)); 
      } 
     } 

     return Math.round((double)sum/numSamples); 

    } 

    /** 

     Gets the frame rate (number of calls to getTime() or 

     addSample() in real time). The frame rate is recalculated 

     every 500ms. 

    */ 

    public float getFrameRate() { 

     long currTime = System.currentTimeMillis(); 



     // calculate the frame rate every 500 milliseconds 

     if (currTime > startTime + FRAME_RATE_RECALC_PERIOD) { 

      frameRate = (float)numFrames * 1000/

       (currTime - startTime); 

      startTime = currTime; 

      numFrames = 0; 

     } 



     return frameRate; 

    } 

} 
+1

我不明白這是如何解決問題的。另外,空行使代碼真的很難遵循。 – 2011-03-23 12:24:28

0

您可以用

LockSupport.parkNanos(long nanos) 

本書雖然它的getter更好的結果複雜的代碼位相比,睡眠()