私は数日間、まったく同じ問題と戦っていました。ループはAndroidの時間参照なしでスムーズに見えますが、あらゆるタイプの「時間同期」が含まれるとすぐに、Android開発制御からの外部要因により、最終結果に深刻な不連続性が生じます。
基本的に、これらの要因は次のとおりです。
- eglSwapIntervalはAndroidに実装されていないため、ハードウェアが画面の最終描画を公開する瞬間を知ることは困難です(ハードウェア画面同期)
- Thread.sleepは正確ではありません。スレッドは、要求されたよりも多かれ少なかれスリープする可能性があります。
- SystemClock.uptimeMillis()System.nanoTime()、System.currentTimeMillis()、およびその他のタイミング関連の測定は正確ではありません(正確です)。
この問題は、描画テクノロジ(描画、openGL 1.0 / 1.1および2.0)およびゲームループ方式(固定時間ステップ、補間、可変時間ステップ)とは無関係です。あなたと同じように、私はThread.sleep、クレイジーな補間、タイマーなどを試していました。あなたが何をするかは関係ありません。私たちはこの要素を制御できません。
このサイトの多くのQ&Aによると、スムーズな連続アニメーションを作成するための基本的なルールは次のとおりです。
- すべての動的メモリ要求を削除して、少なくともGCを減らします。
- ハードウェアがフレームを処理できる速度でフレームをレンダリングします(ほとんどのAndroidデバイスでは40〜60 fpsで問題ありません)。
- 補間または可変時間ステップで固定時間ステップを使用します。
- 更新の物理特性と描画ルーチンを最適化して、ピークの分散が大きくなることなく、比較的一定の時間で実行されるようにします。
確かに、この質問を投稿する前に、updateGame()とdrawGame()を最適化して(かなりのGCと比較的一定の実行時間なしで)、メインループでスムーズなアニメーションを取得するために、以前に多くの作業を行いました。シンプルで絶対にスムーズ」。
stepTimeが可変で、リアルタイムイベント(音楽など)と完全に同期するための特別な要件がない特定のケースでは、ソリューションは単純です。「stepTime変数を滑らかにする」。
このソリューションは、他のゲームループスキーム(可変レンダリングを使用した固定タイムステップ)で機能し、コンセプトを簡単に移植できます(updateGameによって生成される変位の量と複数のフレームにわたるリアルタイムクロックをスムーズにします)。
// avoid GC in your threads. declare nonprimitive variables out of onDraw
float smoothedDeltaRealTime_ms=17.5f; // initial value, Optionally you can save the new computed value (will change with each hardware) in Preferences to optimize the first drawing frames
float movAverageDeltaTime_ms=smoothedDeltaRealTime_ms; // mov Average start with default value
long lastRealTimeMeasurement_ms; // temporal storage for last time measurement
// smooth constant elements to play with
static final float movAveragePeriod=40; // #frames involved in average calc (suggested values 5-100)
static final float smoothFactor=0.1f; // adjusting ratio (suggested values 0.01-0.5)
// sample with opengl. Works with canvas drawing: public void OnDraw(Canvas c)
public void onDrawFrame(GL10 gl){
updateGame(gl, smoothedDeltaRealTime_ms); // divide 1000 if your UpdateGame routine is waiting seconds instead mili-seconds.
drawGame(gl);
// Moving average calc
long currTimePick_ms=SystemClock.uptimeMillis();
float realTimeElapsed_ms;
if (lastRealTimeMeasurement_ms>0){
realTimeElapsed_ms=(currTimePick_ms - lastRealTimeMeasurement_ms);
} else {
realTimeElapsed_ms=smoothedDeltaRealTime_ms; // just the first time
}
movAverageDeltaTime_ms=(realTimeElapsed_ms + movAverageDeltaTime_ms*(movAveragePeriod-1))/movAveragePeriod;
// Calc a better aproximation for smooth stepTime
smoothedDeltaRealTime_ms=smoothedDeltaRealTime_ms +(movAverageDeltaTime_ms - smoothedDeltaRealTime_ms)* smoothFactor;
lastRealTimeMeasurement_ms=currTimePick_ms;
}
// Optional: check if the smoothedDeltaRealTIme_ms is too different from original and save it in Permanent preferences for further use.
固定タイムステップスキームの場合、結果を改善するために中間更新ゲームを実装できます。
float totalVirtualRealTime_ms=0;
float speedAdjustments_ms=0; // to introduce a virtual Time for the animation (reduce or increase animation speed)
float totalAnimationTime_ms=0;
float fixedStepAnimation_ms=20; // 20ms for a 50FPS descriptive animation
int currVirtualAnimationFrame=0; // useful if the updateGameFixedStep routine ask for a frame number
private void updateGame(){
totalVirtualRealTime_ms+=smoothedDeltaRealTime_ms + speedAdjustments_ms;
while (totalVirtualRealTime_ms> totalAnimationTime_ms){
totalAnimationTime_ms+=fixedStepAnimation_ms;
currVirtualAnimationFrame++;
// original updateGame with fixed step
updateGameFixedStep(currVirtualAnimationFrame);
}
float interpolationRatio=(totalAnimationTime_ms-totalVirtualRealTime_ms)/fixedStepAnimation_ms;
Interpolation(interpolationRatio);
}
次のデバイスを使用して、canvasおよびopenGlES10描画でテスト済み:SG SII(57 FPS)、SG Note(57 FPS)、SG tab(60 FPS)、Windows XP(8 FPS)で実行されるブランド化されていないAndroid 2.3(43 FPS)低速エミュレーター。テストプラットフォームは、実際の物理パラメータ(km / hおよびG)で指定されたパスに沿って移動する、約45個のオブジェクトと1個の巨大な背景(70MPソースイメージからのテクスチャ)を描画します。複数のデバイス間でスパイクやフリックは発生しません(エミュレータでは8 FPS)。見た目は良くありませんが、予想通り一定速度で流れます)
アンドロイドがどのように時間を報告するかについてグラフをチェックしてください。Androidが大きなデルタ時間を報告し、次のループだけが平均よりも小さい場合があります。これは、リアルタイム値の読み取りのオフセットを意味します。

詳細:

AndroidのGLSurfaceView.RENDERMODE_CONTINUOUSLYを使用するときにフレームレートを制限するにはどうすればよいですか?
System.currentTimeMillisとSystem.nanoTime
System.currentTimeMillis()メソッドは本当に現在の時刻を返しますか?