私は最近、ゲームプログラミングに取り組もうとしました。私は 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);
}