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 的机器上(我自己测试过,与 Win7 和其他操作系统相比,WinXP 给出了非常糟糕的结果)。我在互联网上闲逛了很多,结果令人失望。这似乎是我们正在运行的操作系统的线程调度程序及其所谓的粒度的错误。据我了解,该调度程序在一定时间内不断检查系统中每个线程的需求,特别是将它们从睡眠中唤醒/唤醒。当重新检查时间很短时,比如 1ms,事情可能看起来很顺利。虽然,据说 WinXP 的粒度高达 10 或 15 毫秒。我还读到,不仅是 Java 程序员,但是那些使用其他语言的人也面临这个问题。知道了这一点,似乎几乎不可能做出一个稳定、坚固、可靠的游戏引擎。然而,它们无处不在。我非常想知道通过哪种方式可以解决或规避这个问题。有经验的人可以给我一个提示吗?

4

3 回答 3

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.nanoTime()代替System.currentTimeMillis().

于 2011-03-23T12:28:00.450 回答
0

也许这对你有帮助。它来自 david brackeen 在 java 中开发游戏并计算平均粒度以伪造更流畅的帧速率: 链接

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;

    }

}
于 2011-03-23T12:15:43.860 回答
0

你可能会得到更好的结果

LockSupport.parkNanos(long nanos) 

尽管与 sleep() 相比,它使代码有点复杂

于 2011-03-23T12:16:18.580 回答