私は最近ゲームプログラミングに取り掛かりました。私はJavaでかなり経験がありますが、ゲームプログラミングではありません。私はhttp://www.koonsolo.com/news/dewitters-gameloop/を読み、ゲームループは、次のコードでも提案実装:「deWiTTERSゲームループ」は一定のUPSを想定していますか?
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()
は、位置を計算したり、敵を動かしたり、衝突を計算したりするようなことを処理します...?補間はupdateGame()
に渡されないので、updateGame()
が正確かつ着実にUPDATES_PER_SECOND
回/秒と呼ばれると仮定していますか?それは、私たちの計算はすべてその仮定に基づいているということですか?どんな理由であれ、updateGame()への呼び出しが遅れていると、それは私たちにとって大きな問題になりませんか?
例えば、私のキャラクタースプライトが右に歩くと仮定して、速度が遅くなると、それはすべてupdateGame()
に移動します - 方法が遅れていると、計算が単純にオフになりキャラクターが遅れることになりますか? 、補間のための次の例が記載されているウェブサイト上
:
10に位置をgametick場合は500であり、速度は100であり、その後11日に位置gametick 600あろう。それでレンダリングすると、どこに車が置かれますか?あなたは最後の配偶者(この場合は500人)の位置をとることができます。しかし、より良い方法は、車が正確に10.3になり場所を予測することであり、これは次のように起こる:
view_position = position + (speed * interpolation)
車はその後、位置530
でレンダリングされます、私はそれは一例です知っています、車の速度をUPDATES_PER_SECOND
に依存させるものではないでしょうか?だから、より多くのUPSはより速い車を意味するでしょうか?これは正しいことはできません...?
何か助け、チュートリアル、何かを感謝します。すべてのヘルプ(!ありがとう)後
UPDATE/SOLUTION
は、ここで私が現在使用しているものです - これまでのところ(文字スプライトを移動するための)かなり良い動作しますが、のは本当に複雑なゲームのものを待ちましょうこれを決める。それでも、私はこれを共有したいと思っていました。
マイゲームループは次のようになります。
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ループは、更新が欠落していることに追いついているので(例えば、レンダリングに時間がかかる場合など)、毎秒25回の更新を確実に行うことができます。最悪の場合:MAX_FRAMESKIP
以上のアップデートが必要です。この場合、アップデートが失われ、ゲームが遅れることになります。それでも、私が言ったように、実験的です。私はこれを実際のデルタに再び変更するかもしれません。- predictes次のゲームの状態?はい。 GameStateは関連するすべてのゲーム情報を保持するオブジェクトであり、レンダラーはこのオブジェクトを渡してゲームをスクリーンに描画します。私の場合は、レンダラーに2つの状態を渡すことにしました:現在の状態、現在のゲーム状態、予測された未来状態、将来は
UPDATE_INTERVAL
です。このようにして、レンダラは補間値を使用して両者の間を簡単に補間することができます。将来のゲーム状態の計算は、実際には非常に簡単です。更新方法(createGameState()
)はデルタ値をとるため、デルタをUPDATE_INTERVAL
だけ増やしてください。これにより、将来の状態が予測されます。将来の状態は、もちろん、ユーザー入力などが同じであることを前提としています。そうでない場合は、次のゲーム状態の更新によって変更が処理されます。 - 残りはほとんど同じで、deWiTTERSゲームループから取られています。
MAX_FRAMESKIP
は、ハードウェアが本当に遅い場合に備えて、私たちが時々何かをレンダリングすることを確実にするためにかなりフェイルセーフです。しかし、これが始動すれば、私はとにかく極端な遅れを持つだろう。補間は以前と同じですが、レンダラーは2つのgamestateの間を単純に補間することができます。補間する数値以外のロジックは必要ありません。それはすばらしい!
多分、明確化のために、例。ここで私は(少し単純化)文字位置を計算する方法である:
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);
}
お返事ありがとうございます。私を混乱させることは、deWITTERがゲームロジックで、デルタ(またはデルタから基本的に計算される補間)をレンダリング関数に渡すことを提案していますが、ゲームロジックを担当する更新関数ではありません。だから両方の関数はデルタ値を一方または別の方法で受け取るべきだと言っていますか? – BlackWolf
デルタは更新メソッドでのみ必要となります。なぜなら、これはゲームの世界を変更する場所だからです。レンダリングメソッドは、[ゲームワールドモデル](http://stackoverflow.com/tags/model/info)のみを読み込むだけで十分です。それは世界の表現を作りますが、その状態を変えるべきではありません。したがって、レンダリングメソッドのデルタは不要です。 – Lucius
さて、そうですね、そういう意味で私はそれを実装すると思うのですが、これは私にとっても意味をなさないからです。 sidenoteとして、デルタをレンダリングに渡すことは依然として有用であると考えています.2つのゲームのアップデート間で動きを補間することができます。そうでなければ、毎秒25回のゲーム更新があり、毎秒200回のレンダリング更新があった場合、基本的に175のレンダリングが無駄になります。 あなたが言ったように、このことは車が障害物を "通過"することを意味することがありますが、更新メソッドが十分に頻繁に呼び出され、これが起こらない/気づかれない可能性があります。 – BlackWolf