2011-03-23 80 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更好的结果复杂的代码位相比,睡眠()