Android ゲームを作成していますが、現在、希望するパフォーマンスが得られません。オブジェクトの位置を更新する独自のスレッドにゲームループがあります。レンダリング スレッドは、これらのオブジェクトを走査して描画します。現在の動作は、途切れ途切れ/不均一な動きのように見えます。私が説明できないのは、更新ロジックを独自のスレッドに配置する前に、gl 呼び出しの直前に onDrawFrame メソッドにあったということです。その場合、アニメーションは完全にスムーズでしたが、Thread.sleep を介して更新ループを調整しようとすると、途切れ途切れ/不均一になるだけです。更新スレッドが暴走する (スリープしない) ことを許可しても、アニメーションはスムーズですが、Thread.sleep が関係している場合のみ、アニメーションの品質に影響します。
問題を再現できるかどうかを確認するために、スケルトン プロジェクトを作成しました。以下は、更新ループとレンダラーの onDrawFrame メソッドです: 更新ループ
@Override
public void run()
{
while(gameOn)
{
long currentRun = SystemClock.uptimeMillis();
if(lastRun == 0)
{
lastRun = currentRun - 16;
}
long delta = currentRun - lastRun;
lastRun = currentRun;
posY += moveY*delta/20.0;
GlobalObjects.ypos = posY;
long rightNow = SystemClock.uptimeMillis();
if(rightNow - currentRun < 16)
{
try {
Thread.sleep(16 - (rightNow - currentRun));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
そして、ここに私のonDrawFrameメソッドがあります:
@Override
public void onDrawFrame(GL10 gl) {
gl.glClearColor(1f, 1f, 0, 0);
gl.glClear(GL10.GL_COLOR_BUFFER_BIT |
GL10.GL_DEPTH_BUFFER_BIT);
gl.glLoadIdentity();
gl.glBindTexture(GL10.GL_TEXTURE_2D, textures[0]);
gl.glTranslatef(transX, GlobalObjects.ypos, transZ);
//gl.glRotatef(45, 0, 0, 1);
//gl.glColor4f(0, 1, 0, 0);
gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
gl.glVertexPointer(3, GL10.GL_FLOAT, 0, vertexBuffer);
gl.glTexCoordPointer(2, GL10.GL_FLOAT, 0, uvBuffer);
gl.glDrawElements(GL10.GL_TRIANGLES, drawOrder.length,
GL10.GL_UNSIGNED_SHORT, indiceBuffer);
gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);
gl.glDisableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
}
レプリカ アイランドのソースを調べたところ、彼は別のスレッドで更新ロジックを実行し、Thread.sleep で調整していますが、彼のゲームは非常にスムーズに見えます。誰かが私が説明していることについて何か考えを持っているか、誰かが経験したことがありますか?
---編集: 2013 年 1 月 25 日 ---
考える時間があり、このゲーム エンジンを大幅に滑らかにしました。私がこれをどのように管理したかは、実際のゲーム プログラマーにとって冒涜的または侮辱的である可能性があるため、これらの考えを自由に修正してください。
基本的な考え方は、更新、描画...更新、描画...のパターンを維持しながら、時間のデルタを比較的同じに保つことです(多くの場合、制御できません)。私の最初の行動方針は、レンダラーが許可されたという通知を受けた後にのみ描画されるように、レンダラーを同期することでした。これは次のようになります。
public void onDrawFrame(GL10 gl10) {
synchronized(drawLock)
{
while(!GlobalGameObjects.getInstance().isUpdateHappened())
{
try
{
Log.d("test1", "draw locking");
drawLock.wait();
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
}
更新ロジックを終了したら、drawLock.notify() を呼び出し、レンダリング スレッドを解放して、更新したものを描画します。これの目的は、更新、描画、更新、描画などのパターンを確立するのに役立つことです。
それを実装すると、かなりスムーズになりましたが、それでも時々動きがジャンプすることがありました. いくつかのテストの後、ondrawFrame の呼び出しの間に複数の更新が発生していることがわかりました。これにより、1 つのフレームに 2 つ (またはそれ以上) の更新の結果が表示され、通常よりも大きなジャンプが発生していました。
これを解決するために私がしたことは、2 つの onDrawFrame 呼び出しの間の時間デルタをある値 (たとえば 18 ミリ秒) に制限し、余分な時間を残りに格納することでした。この残りは、処理できる場合、次の数回の更新で後続の時間デルタに分配されます。このアイデアは、すべての突然のロング ジャンプを防止し、本質的に、複数のフレームにわたるタイム スパイクをスムーズにします。これを行うと、素晴らしい結果が得られました。
このアプローチの欠点は、しばらくの間、オブジェクトの位置が時間の経過とともに正確ではなくなり、その差を補うために実際に速度が上がることです。しかし、それはよりスムーズで、速度の変化はあまり目立ちません.
最後に、最初に作成したエンジンにパッチを適用するのではなく、上記の 2 つのアイデアを念頭に置いてエンジンを書き直すことにしました。おそらく誰かがコメントできるスレッド同期の最適化を行いました。
現在のスレッドは次のようにやり取りします:
-更新スレッドは現在のバッファーを更新し (更新と描画を同時に行うためのダブル バッファー システム)、前のフレームが描画されている場合は、このバッファーをレンダラーに渡します。
- 前のフレームがまだ描画されていない、または描画中の場合、更新スレッドは、レンダリング スレッドが描画したことを通知するまで待機します。
-レンダリング スレッドは、更新が発生したことを更新スレッドから通知されるまで待機します。
-レンダー スレッドが描画するとき、最後に描画した 2 つのバッファーを示す "最後に描画された変数" を設定し、前のバッファーが描画されるのを待っていた場合は更新スレッドに通知します。
少し複雑かもしれませんが、マルチスレッドの利点を考慮して、フレーム n-1 が描画されている間にフレーム n の更新を実行できると同時に、レンダラーが長い時間。さらに説明すると、この複数の更新シナリオは、lastDrawn バッファーが更新されたばかりのバッファーと等しいことが検出された場合、更新スレッドのロックによって処理されます。それらが等しい場合、これは前のフレームがまだ描画されていないことを更新スレッドに示します。
これまでのところ、私は良い結果を得ています。誰かコメントがあれば教えてください。私がしていることについて、正しいか間違っているかについて、あなたの考えを聞かせてください。
ありがとう