正確なFPS
正確なFPS(Frame Per Second)を計算して画面に表示します。正確なFPSの実現方法は、『Killer Game Programming in Java』を参考にしました。
ゲームループの構造
ゲームループは下のような構造になっています。詳しくは、アクティブレンダリング(2006/5/7)を参照。
/** * ゲームループ */ public void run() { while (true) { gameUpdate(); // ゲーム状態を更新(ex: ボールの移動) gameRender(); // バッファにレンダリング(ダブルバッファリング) paintScreen(); // バッファを画面に描画(repaint()を自分でする!) // 休止 try { Thread.sleep(20); } catch (InterruptedException e) { e.printStackTrace(); } } }
1フレームというのはゲームループ1回分のことです。つまり、1フレームで行う必要のある処理は、
- ゲーム状態の更新(gameUpdate)
- バッファにレンダリング(gameRender)
- バッファ内容を画面に描画(paintScreen)
- 休止(sleep)
です。ここで休止が必要なのは1つのプログラムがCPUを独占してしまうのを防ぐためです。ゲームループが休止している間に他のプログラムが実行されます(ゲームしながらインターネットできるのはこのためです)。
たとえば、FPSを50とします。これは1秒間に50フレームという意味です。つまり、1秒間に50回ゲームループがまわります。上の4つの処理を1秒間に50回繰り返すってことですね。1秒間に50フレームということは1フレームあたり1/50=0.02秒=20ミリ秒使うことができます。つまり、上の4つの処理の実行に20ミリ秒間だけ使えます。
時間の単位
1s(秒)=1000ms(ミリ秒)=1000000μs(マイクロ秒)=1000000000ns(ナノ秒)
Javaでは時間の測定によくSystem.currentTimeMillis()が使われます。このメソッドは、経過時間をミリ秒単位で返しますが精度に問題があります。
JDK1.5からはSystem.nanoTime()というメソッドが追加されています。このメソッドは、経過時間をナノ秒単位で返す非常に高精度なタイマーです。今回のFPSの計算でもnanoTime()を使っています。そのため、JDK1.5以降のバージョンのJavaをインストールする必要がありますので注意してください。
正確なFPS
正確なFPSを実現するにはゲームループで少し工夫が必要です。ここでは、
- 正確な休止時間を計算する
- 休止できないときでも定期的に他のスレッドに制御を移す
- 休止にもちいるThread.sleep()の誤差を吸収する
という3つの工夫をとっています。
public void run() { long beforeTime, afterTime, timeDiff, sleepTime; long overSleepTime = 0L; int noDelays = 0; beforeTime = System.nanoTime(); prevCalcTime = beforeTime; running = true; while (running) { gameUpdate(); gameRender(); paintScreen(); afterTime = System.nanoTime(); timeDiff = afterTime - beforeTime; // 前回のフレームの休止時間誤差も引いておく sleepTime = (PERIOD - timeDiff) - overSleepTime; if (sleepTime > 0) { // 休止時間がとれる場合 try { Thread.sleep(sleepTime / 1000000L); // nano->ms } catch (InterruptedException e) { e.printStackTrace(); } // sleep()の誤差 overSleepTime = (System.nanoTime() - afterTime) - sleepTime; } else { // 状態更新・レンダリングで時間を使い切ってしまい // 休止時間がとれない場合 overSleepTime = 0L; // 休止なしが16回以上続いたら if (++noDelays >= 16) { Thread.yield(); // 他のスレッドを強制実行 noDelays = 0; } } beforeTime = System.nanoTime(); // FPSを計算 calcFPS(); } System.exit(0); }
上のコードを図示するとこんな感じです。
FPSの計算
FPSの計算はcalcFPS()で行います。このメソッドは、1秒間のフレーム数(frameCount)を経過時間(大体1秒)で割ってFPS を計算しています。時間計測の単位がナノ秒なのでFPSを計算するときに秒に変換しています。actualFPSに格納されたFPS値はパネル上に描画されます。clacFPS()はゲームループ内で呼び出しています。
/** * FPSの計算 * */ private void calcFPS() { frameCount++; calcInterval += PERIOD; // 1秒おきにFPSを再計算する if (calcInterval >= MAX_STATS_INTERVAL) { long timeNow = System.nanoTime(); // 実際の経過時間を測定 long realElapsedTime = timeNow - prevCalcTime; // 単位: ns // FPSを計算 // realElapsedTimeの単位はnsなのでsに変換する actualFPS = ((double) frameCount / realElapsedTime) * 1000000000L; System.out.println(df.format(actualFPS)); frameCount = 0L; calcInterval = 0L; prevCalcTime = timeNow; } }