Java游戏循环中的精确计时

Java游戏循环中的精确计时,java,network-programming,timing,game-loop,Java,Network Programming,Timing,Game Loop,我正在开发一个基于网络的游戏,现在主要关注服务器端模拟。当然,我需要一个游戏循环,我选择了一个固定的timestep循环,这样在客户机上复制比在可变timestep循环容易得多。我还决定以60赫兹的频率运行我的游戏。这是游戏的逻辑速度,而不是渲染速度。渲染将通过客户端中的可变timestep循环进行处理,以获得最佳的渲染效果。 服务器是用Java编写的 我已经使用源代码制作了一个示例游戏循环,并使用我的代码修改了循环。以下是循环: private void gameLoop() { fina

我正在开发一个基于网络的游戏,现在主要关注服务器端模拟。当然,我需要一个游戏循环,我选择了一个固定的timestep循环,这样在客户机上复制比在可变timestep循环容易得多。我还决定以60赫兹的频率运行我的游戏。这是游戏的逻辑速度,而不是渲染速度。渲染将通过客户端中的可变timestep循环进行处理,以获得最佳的渲染效果。 服务器是用Java编写的

我已经使用源代码制作了一个示例游戏循环,并使用我的代码修改了循环。以下是循环:

private void gameLoop()
{
  final double GAME_HERTZ = 60.0;
  final double TIME_BETWEEN_UPDATES = 1000000000 / GAME_HERTZ;
  //We will need the last update time.
  double lastUpdateTime = System.nanoTime();
  //Store the last time we rendered.
  double lastRenderTime = System.nanoTime();

  int lastSecondTime = (int) (lastUpdateTime / 1000000000);

  long extraSleepTime = 0;
  while (running)
  {
     int updateCount = 0;

     if (!paused)
     {
        long loopStartTime = System.nanoTime();
        updateGame();
        updateCount++;
        long timeAfterUpdate = System.nanoTime();
        lastUpdateTime = timeAfterUpdate;

        //Render. To do so, we need to calculate interpolation for a smooth render.
        float interpolation = Math.min(1.0f, (float) ((loopStartTime - lastUpdateTime) / TIME_BETWEEN_UPDATES) );
        drawGame(interpolation);
        lastRenderTime = loopStartTime;

        //Update the frames we got.
        int thisSecond = (int) (lastUpdateTime / 1000000000);
        if (thisSecond > lastSecondTime)
        {
            long nanoTime = System.nanoTime();
           System.out.println("NEW SECOND " + thisSecond + " " + frameCount + ": " + (nanoTime - lastNanoTime));
           lastNanoTime = nanoTime;
           fps = frameCount;
           frameCount = 0;
           lastSecondTime = thisSecond;
        }

        long loopExecutionTime = timeAfterUpdate - loopStartTime;
        long sleepTime = (long)TIME_BETWEEN_UPDATES - loopExecutionTime - extraSleepTime;
        // Only sleep for positive intervals
        if(sleepTime >= 0)
        {
            try
            {
                Thread.sleep(sleepTime / 1000000);
            }
            catch(InterruptedException e) {}
        }
        else
        {
            System.out.println("WARN: sleepTime < 0");
        }
        // Counts the extra time that elapsed
        extraSleepTime = System.nanoTime() - timeAfterUpdate - sleepTime;
     }
  }
private void gameLoop()
{
最后一场双人赛,赫兹=60.0;
两次更新之间的最终双倍时间=100000000/游戏赫兹;
//我们需要最后的更新时间。
double lastUpdateTime=System.nanoTime();
//存储上次渲染的时间。
double lastRenderTime=System.nanoTime();
int lastSecondTime=(int)(lastUpdateTime/100000000);
长睡眠时间=0;
(跑步时)
{
int updateCount=0;
如果(!暂停)
{
long loopStartTime=System.nanoTime();
updateGame();
updateCount++;
long-timeAfterUpdate=System.nanoTime();
lastUpdateTime=timeAfterUpdate;
//为此,我们需要计算平滑渲染的插值。
浮点插值=Math.min(1.0f,(float)((loopStartTime-lastUpdateTime)/两次更新之间的时间);
抽签游戏(插值);
lastRenderTime=loopStartTime;
//更新我们得到的帧。
int thissond=(int)(lastUpdateTime/100000000);
如果(thisSecond>lastSecondTime)
{
long nanoTime=System.nanoTime();
System.out.println(“新秒”+此秒+“”+帧数+:“+(nanoTime-lastnotime));
lastnotime=纳米时间;
fps=帧数;
帧数=0;
lastSecondTime=此秒;
}
long loopExecutionTime=timeAfterUpdate-loopStartTime;
长睡眠时间=(长)两次更新之间的时间\u-loopExecutionTime-extraSleepTime;
//只有积极的睡眠时间间隔
如果(睡眠时间>=0)
{
尝试
{
线程睡眠(睡眠时间/1000000);
}
捕获(中断异常e){}
}
其他的
{
System.out.println(“警告:睡眠时间<0”);
}
//计算经过的额外时间
extraSleepTime=System.nanoTime()-timeAfterUpdate-sleepTime;
}
}
问题是,在运行时,FPS在60Hz时不稳定,但有时会降低。例如,我有时会得到58-59Hz,低至57Hz。 如果游戏在本地运行,这种可变性不会成为问题,但由于我们的游戏是联网的,我需要保持准确的时间,以便在客户端和服务器上重现逻辑计算

这段代码中有没有错误,或者有什么可以改进以使其更稳定的地方?我们的目标是一直精确地保持60Hz

编辑:我想到的第一个解决方案是以比需要快一点的速度运行循环,例如在70Hz,并检查帧计数以将更新限制为每秒60次。这样模拟将以突发方式运行,需要缓冲(一次最多60帧),但决不能比需要的速度慢


提前感谢。

< P>如果你想达到每秒60帧,最好使用一个预定的执行器,作为<代码>线程。SLePER()/code >可能不像你希望的那样精确。考虑下面的示例,为你的服务器代码:(请注意它包含java 8代码)< /P>
它将运行您的
gameLoop()
每16毫秒一次,这基本上就是你想要的。这应该会给你更精确的结果。你也可以用纳秒对应的时间单位替换16和
时间单位。毫秒
,即使它不会产生任何明显的差异

这样循环会运行得更快,尽管1000/60是16666…,not仅16。根据我在问题中发布的代码,我认为它会根据需要调整延迟(例如,在16毫秒时执行一些帧,在17毫秒时执行一些帧),不是吗?是的,你在两种情况下都是对的。然而,在实践中,计划执行者很可能会更“稳定”,而Thread.sleep可能有不同的结果。在同一问题上,使用16666(6)可以使执行器更精确一些…并将时间单位更改为纳秒。在任何情况下,这两种方法都可以完成任务,不会打扰您。我想说的是,先完成游戏的样板并试一试。在实践中,您将看不到服务器以57或60 fps运行的区别。
    public void gameLoop() {
       // game logic here
    }

    Executors.newSingleThreadScheduledExecutor()
             .scheduleAtFixedRate(this::gameLoop, 0, 16, TimeUnit.MILLISECONDS)