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が、どれだけスリープするかについての期待を裏切るのが大好きなことです。特にWindowsXPを搭載した一部のマシンでは、スレッドをはるかに長い時間またははるかに短い時間で簡単に停止できます(私は自分でテストしましたが、WinXPはWin7や他のOSと比較して非常に厄介な結果をもたらします)。私はインターネットをかなり調べてきましたが、結果は期待外れでした。実行しているOSのスレッドスケジューラと、いわゆる粒度のせいのようです。私が理解している限り、このスケジューラーは、一定の時間にわたって、システム内のすべてのスレッドの要求を常にチェックし、特に、スレッドをスリープ状態から復帰させます。再確認時間が1msのように短い場合、スムーズに見える場合があります。ただし、WinXPの粒度は10〜15ミリ秒と言われています。また、Javaプログラマーだけでなく、しかし、他の言語を使用している人もこの問題に直面しています。これを知っていると、安定した、頑丈で、信頼できるゲームエンジンを作ることはほとんど不可能のようです。それにもかかわらず、彼らはいたるところにいます。どういう意味でこの問題と戦ったり回避したりできるのだろうかと非常に思っています。もっと経験豊富な人が私にこれについてのヒントを教えてもらえますか?

4

3 に答える 3

2

正確な時点または正確な遅延後にスレッドをウェイクアップしたり、コールバックを呼び出したりするために、OSやタイマーメカニズムに依存しないでください。それは起こらないだろう。

これに対処する方法は、スリープ/コールバック/ポーリング間隔を設定し、間隔が高精度に維持されていると想定する代わりに、前の反復から経過した時間を追跡し、それを使用して現在の状態を決定します。この量を、現在の「フレーム」に基づいて状態を更新するものに渡します(実際には、内部コンポーネントがフレームのように具体的なものを認識または気にしないようにエンジンを設計する必要があります。その代わりに、時間の経過とともに流動的に移動する状態、およびこの状態のスナップショットをレンダリングするために新しいフレームを送信する必要がある場合が使用されます)。

したがって、たとえば、次のようにします。

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

多分これはあなたを助けます。これは、Javaでゲームを開発しているdavid brackeenのボックからのものであり、平均粒度を計算して、より流暢なフレームレートを偽造します。 リンク

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 に答える