8

私は最近、ゲームプログラミングに取り組もうとしました。私は Java の経験は豊富ですが、ゲーム プログラミングの経験はありません。http://www.koonsolo.com/news/dewitters-gameloop/を読み、そこで提案されたゲーム ループを次のコードで実装しました。

private static int UPDATES_PER_SECOND = 25;
private static int UPDATE_INTERVAL = 1000 / UPDATES_PER_SECOND * 1000000;
private static int MAX_FRAMESKIP = 5;

public void run() {
    while (true) {
        int skippedFrames = 0;
        while (System.nanoTime() > this.nextUpdate && skippedFrames < MAX_FRAMESKIP) {
            this.updateGame();
            this.nextUpdate += UPDATE_INTERVAL;
            skippedFrames++;
        }

        long currentNanoTime = System.nanoTime();
        double interpolation = (currentNanoTime + UPDATE_INTERVAL - this.nextUpdate) / UPDATE_INTERVAL;
        this.repaintGame(interpolation);
    }
}

ループは有望で簡単に見えましたが、実際に何かをしようとしているので、もうよくわかりません。私が完全に間違っていなければupdateGame()、位置の計算、敵の移動、衝突の計算などを処理します...? 補間が に渡されないので、 が1 秒あたり正確かつ着実に呼び出されるとupdateGame()仮定することを意味しますか? それは、すべての計算がその仮定に基づいているということですか? そして、何らかの理由で updateGame() の呼び出しが遅れると、多くの問題が発生するのではないでしょうか? たとえば、私のキャラクター スプライトが右に歩くことになっていて、毎秒その速度に応じて移動するとします。updateGame()UPDATES_PER_SECOND
updateGame()- メソッドが遅れた場合、それは単に計算がオフになり、キャラクターが遅れることを意味しますか?

Web サイトでは、補間の次の例が記載されています。

10 番目のゲームティックで位置が 500 で速度が 100 の場合、11 番目のゲームティックで位置は 600 になります。では、レンダリング時に車をどこに配置しますか? 最後のゲームティック (この場合は 500) の位置を取ることができます。しかし、より良い方法は、自動車が正確に 10.3 の位置にあることを予測することです。これ
view_position = position + (speed * interpolation)
は次のように行われます。自動車は 530 の位置にレンダリングされます。

私はそれが単なる例であることを知っていますが、それは車の速度をに依存させUPDATES_PER_SECONDませんか? では、UPS が増えると、車が速くなりますか? これはいけません…?

ヘルプ、チュートリアル、何でも感謝します。

更新/解決策

すべての助け(ありがとう!)の後、これが私が現在使用しているものです-これまでのところ(キャラクタースプライトを移動するために)かなりうまく機能しますが、本当に複雑なゲームがこれを決定するのを待ちましょう. それでも、私はこれを共有したかったのです。
私のゲーム ループは次のようになります。

private static int UPDATES_PER_SECOND = 25;
private static int UPDATE_INTERVAL = 1000 / UPDATES_PER_SECOND * 1000000;
private static int MAX_FRAMESKIP = 5;

private long nextUpdate = System.nanoTime();

public void run() {
    while (true) {
        int skippedFrames = 0;
        while (System.nanoTime() > this.nextUpdate && skippedFrames < MAX_FRAMESKIP) {
            long delta = UPDATE_INTERVAL;
            this.currentState = this.createGameState(delta);
            this.newPredictedNextState = this.createGameState(delta + UPDATE_INTERVAL, true);

            this.nextUpdate += UPDATE_INTERVAL;
            skippedFrames++;
        }

        double interpolation = (System.nanoTime() + UPDATE_INTERVAL - this.nextUpdate) / (double) UPDATE_INTERVAL;
        this.repaintGame(interpolation);
    }
}

ご覧のとおり、いくつかの変更があります。

  • delta = UPDATE_INTERVAL? はい。これはまだ実験段階ですが、うまくいくと思います。問題は、実際に 2 つのタイムスタンプからデルタを計算するとすぐに、浮動計算エラーが発生することです。それらは小さいですが、更新が何百万回も呼び出されることを考えると、合計されます。また、2 番目の while ループにより、見逃した更新に追いつくことができるため (たとえば、レンダリングに時間がかかる場合)、1 秒あたり 25 回の更新を確実に取得できます。最悪の場合:MAX_FRAMESKIP更新以外のことも見逃します。この場合、更新が失われ、ゲームが遅くなります。それでも、私が言ったように、実験的です。これを実際のデルタに変更するかもしれません。
  • 次のゲームの状態を予測しますか? はい。GameState は、関連するすべてのゲーム情報を保持するオブジェクトです。レンダラーはこのオブジェクトを渡されて、ゲームを画面にレンダリングします。私の場合、レンダラーに 2 つの状態を与えることにしました。現在のゲームの状態と、将来の予測される将来の状態で、通常渡すものUPDATE_INTERVALです。このようにして、レンダラーは補間値を使用して、両方の間を簡単に補間できます。将来のゲームの状態を計算するのは実際には非常に簡単です - update メソッド ( createGameState()) はとにかくデルタ値を取るので、デルタを増やすだけですUPDATE_INTERVAL- この方法で将来の状態が予測されます。もちろん、将来の状態は、ユーザー入力などが同じままであることを前提としています。そうでない場合は、次のゲーム状態の更新で変更が処理されます。
  • 残りはほぼ同じままで、deWiTTERS ゲーム ループから取得されます。MAX_FRAMESKIPハードウェアが非常に遅い場合に備えて、時々何かをレンダリングすることを確認するためのフェイルセーフです。しかし、これが開始された場合、とにかく極端なラグが発生すると思います. 補間は以前と同じですが、レンダラーは 2 つのゲームステート間を単純に補間できるようになりました。数値を補間する以外のロジックは必要ありません。それはすばらしい!

明確にするために、例を挙げます。文字の位置を計算する方法は次のとおりです(少し簡略化されています):

public GameState createGameState(long delta, boolean ignoreNewInput) {
    //Handle User Input and stuff if ignoreNewInput=false

    GameState newState = this.currentState.copy();
    Sprite charSprite = newState.getCharacterSprite();
    charSprite.moveByX(charSprite.getMaxSpeed() * delta * charSprite.getMoveDirection().getX()); 
    //getMoveDirection().getX() is 1.0 when the right arrow key is pressed, otherwise 0.0
}

…では、ウィンドウのペイントメソッドで…

public void paint(Graphics g) {
    super.paint(g);

    Graphics2D g2d = (Graphics2D) g;

    Sprite currentCharSprite = currentGameState.getCharacterSprite();
    Sprite nextCharSprite = predictedNextState.getCharacterSprite(); 
    Position currentPos = currentCharSprite.getPosition();
    Position nextPos = nextCharSprite.getPosition();
    //Interpolate position
    double x = currentPos.getX() + (nextPos.getX() - currentPos.getX()) * this.currentInterpolation;
    double y = currentPos.getY() + (nextPos.getY() - currentPos.getY()) * this.currentInterpolation;
    Position interpolatedCharPos = new Position(x, y);
    g2d.drawImage(currentCharSprite.getImage(), (int) interpolatedCharPos.getX(), (int) interpolatedCharPos.getY(), null);
}
4

1 に答える 1

4

更新間隔が一定であるという仮定に基づいてゲーム ロジックを作成しないでください。2 つの更新の間に経過した時間を正確に測定するゲーム クロックを含めます。その後、すべての計算を遅延に基づいて行うことができ、実際の更新レートについて心配する必要はありません。

この場合、車の速度は単位/秒で与えられ、デルタは最後の更新以降の合計秒数になります。

car.position += car.velocity * delta;

ゲーム ロジックの更新とフレームの描画を分離するのが一般的な方法です。おっしゃる通り、フレームのレンダリングを時々スキップすることで、安定した更新レートを維持することができます。

しかし、更新間隔を一定に保つことはそれほど重要ではありません。単位時間あたりの更新数を最小限に抑えることは非常に重要です。障害物に向かって非常に高速で移動する車両を想像してください。更新頻度が低すぎると、2 つの更新間の移動距離が障害物の合計サイズよりも大きくなる可能性があります。車両はそれを通り抜けます。

于 2013-02-26T22:30:25.733 に答える