9

さまざまなゲームループソリューションに関するKoenWittersの詳細な記事を読んでいますが、推奨されるものであるGLUTを使用して最後のソリューションを実装する際に問題が発生しています。

一定のゲーム速度を達成する方法に関する他の人々からのいくつかの記事、チュートリアル、およびコードを読んだ後、私が現在実装しているもの(以下のコードを投稿します)は、KoenWittersが可変FPSに依存するゲーム速度と呼んだものだと思います、彼の記事の2番目。

まず、私の検索経験を通して、おそらくこれを支援する知識を持っているが、GLUTが何であるかを知らない人が何人かいます。私は、関連する機能を説明しようとします(私を修正してください)。このOpenGLツールキットの私の問題。GLUTとは何か、GLUTの操作方法を知っている場合は、このセクションをスキップしてください。

GLUT Toolkit:

  • GLUTはOpenGLツールキットであり、OpenGLの一般的なタスクを支援します。
  • は、すべてのレンダリングを担当する関数コールバックglutDisplayFunc(renderScene)へのポインタを取ります。renderScene()このrenderScene()関数は、コールバック登録後に1回だけ呼び出されます。
  • glutTimerFunc(TIMER_MILLISECONDS, processAnimationTimer, 0)、コールバックを呼び出す前に通過するミリ秒数を要しますprocessAnimationTimer()。最後の引数は、タイマーコールバックに渡す値です。はそれぞれではなく、一度だけprocessAnimationTimer()呼び出されます。TIMER_MILLISECONDS
  • このglutPostRedisplay()関数はGLUTに新しいフレームをレンダリングするように要求するため、シーン内で何かを変更するたびにこれを呼び出す必要があります。
  • にコールバックを登録するために使用glutIdleFunc(renderScene)できますがrenderScene()(これは無関係にはなりませんglutDisplayFunc())、イベントが受信されていないときにアイドルコールバックが継続的に呼び出され、CPU負荷が増加するため、この関数は避ける必要があります。
  • このglutGet(GLUT_ELAPSED_TIME)関数は、glutInit呼び出されてからのミリ秒数(またはの最初の呼び出しglutGet(GLUT_ELAPSED_TIME))を返します。これがGLUTのタイマーです。高解像度タイマーのより良い代替案があることは知っていますが、今のところこれを維持しましょう。

これは、GLUTがフレームをレンダリングする方法についての十分な情報であると思います。そのため、GLUTについて知らなかった人も、この質問に答えて、気に入らなかった場合に助けてもらうことができます。

現在の実装:

さて、Koenによって提案された2番目のソリューションである可変FPSに依存するゲーム速度を正しく実装したかどうかはわかりません。そのための関連コードは次のようになります。

#define TICKS_PER_SECOND 30
#define MOVEMENT_SPEED 2.0f

const int TIMER_MILLISECONDS = 1000 / TICKS_PER_SECOND;

int previousTime;
int currentTime;
int elapsedTime;

void renderScene(void) {
    (...)

    // Setup the camera position and looking point
    SceneCamera.LookAt();

    // Do all drawing below...

    (...)
}

void processAnimationTimer(int value) {
    // setups the timer to be called again
    glutTimerFunc(TIMER_MILLISECONDS, processAnimationTimer, 0);

    // Get the time when the previous frame was rendered
    previousTime = currentTime;

    // Get the current time (in milliseconds) and calculate the elapsed time
    currentTime = glutGet(GLUT_ELAPSED_TIME);
    elapsedTime = currentTime - previousTime;

    /* Multiply the camera direction vector by constant speed then by the
       elapsed time (in seconds) and then move the camera */
    SceneCamera.Move(cameraDirection * MOVEMENT_SPEED * (elapsedTime / 1000.0f));

    // Requests to render a new frame (this will call my renderScene() once)
    glutPostRedisplay();
}

void main(int argc, char **argv) {
    glutInit(&argc, argv);

    (...)

    glutDisplayFunc(renderScene);

    (...)

    // Setup the timer to be called one first time
    glutTimerFunc(TIMER_MILLISECONDS, processAnimationTimer, 0);
    // Read the current time since glutInit was called
    currentTime = glutGet(GLUT_ELAPSED_TIME);

    glutMainLoop();
}

この実装は正しくありません。これは、ゲームの速度をFPSに依存して一定にするのに役立つという意味で機能します。そのため、ポイントAからポイントBへの移動には、高/低フレームレートに関係なく同じ時間がかかります。ただし、このアプローチではゲームのフレームレートを制限していると思います。[編集:各フレームは、タイムコールバックが呼び出されたときにのみレンダリングされます。つまり、フレームレートはおよそTICKS_PER_SECOND1秒あたりのフレーム数になります。これは正しくないと感じます。強力なハードウェアを制限するべきではありません。間違っています。ただし、まだ計算する必要があることは私の理解ですelapsedTime。GLUTにタイマーコールバックを毎回呼び出すように指示しているからといってTIMER_MILLISECONDS、それが常に時間どおりに行われるとは限りません。]

どうすればこれを修正できるかわかりません。正直に言うと、GLUTのゲームループが何であるかわかりません。Koenwhile( game_is_running )の記事のループです。[編集: GLUTはイベント駆動型であり、電話をかけるとゲームループが開始することを理解していますglutMainLoop()(これは決して戻りません)、そうですか?]

アイドル状態のコールバックをに登録し、glutIdleFunc()それをの代わりに使用してglutTimerFunc()、必要な場合にのみレンダリングできると思いました(通常のように常にではなく)が、これを空のコールバック(のようにvoid gameLoop() {})でテストしたところ、基本的に何も行われず、画面が黒い場合、CPUは25%に急上昇し、ゲームを終了して通常に戻るまでそこに留まりました。だから私はそれがたどる道ではないと思います。

glutTimerFunc()ゲームをクールではなく一定のFPSに制限しているため、使用することは、それに基づいてすべての動き/アニメーションを実行するための良いアプローチではありません。または、間違って使用していて、実装が正しくない可能性がありますか?

可変FPSで一定のゲーム速度をどの程度正確に得ることができますか?より正確には、GLUTを使用してKoenのConstant Game Speed with Maximum FPSソリューション(彼の記事の4番目のソリューション)を正しく実装するにはどうすればよいですか?たぶんこれはGLUTではまったく不可能ですか?そうでない場合、私の選択肢は何ですか?GLUTを使用したこの問題(一定のゲーム速度)への最善のアプローチは何ですか?

[編集]別のアプローチ:

私は実験を続けてきましたが、これが私が今達成できたことです。時間指定関数(ゲームのフレームレートを制限する)で経過時間を計算する代わりに、現在はで計算していますrenderScene()。シーンに変更が発生するたびにglutPostRedisplay()(つまり、カメラの移動、オブジェクトアニメーションなど)、を呼び出しますrenderScene()。この機能の経過時間を使って、例えばカメラを動かすことができます。

私のコードはこれに変わりました:

int previousTime;
int currentTime;
int elapsedTime;

void renderScene(void) {
    (...)

    // Setup the camera position and looking point
    SceneCamera.LookAt();

    // Do all drawing below...

    (...)
}

void renderScene(void) {
    (...)

    // Get the time when the previous frame was rendered
    previousTime = currentTime;

    // Get the current time (in milliseconds) and calculate the elapsed time
    currentTime = glutGet(GLUT_ELAPSED_TIME);
    elapsedTime = currentTime - previousTime;

    /* Multiply the camera direction vector by constant speed then by the
       elapsed time (in seconds) and then move the camera */
    SceneCamera.Move(cameraDirection * MOVEMENT_SPEED * (elapsedTime / 1000.0f));

    // Setup the camera position and looking point
    SceneCamera.LookAt();

    // All drawing code goes inside this function
    drawCompleteScene();

    glutSwapBuffers();

    /* Redraw the frame ONLY if the user is moving the camera
       (similar code will be needed to redraw the frame for other events) */
    if(!IsTupleEmpty(cameraDirection)) {
        glutPostRedisplay();
    }
}

void main(int argc, char **argv) {
    glutInit(&argc, argv);

    (...)

    glutDisplayFunc(renderScene);

    (...)

    currentTime = glutGet(GLUT_ELAPSED_TIME);

    glutMainLoop();
}

結論として、それは機能している、またはそう思われます。カメラを動かさない場合、CPU使用率は低く、何もレンダリングされていません(テスト目的では、グリッドは4000.0fに拡張されていますが、zFarは1000.0fに設定されています)。カメラを動かし始めると、シーンはそれ自体を再描画し始めます。移動キーを押し続けると、CPU使用率が高くなります。これは正常な動作です。動かなくなると元に戻ります。

私が何かを逃していない限り、それは今のところ良いアプローチのようです。私はiDevGamesでこの興味深い記事を見つけました。この実装は、おそらくその記事で説明されている問題の影響を受けています。それについてどう思いますか?

私はただ楽しみのためにこれをしていることに注意してください。少なくとも近い将来ではなく、配布するゲームなどを作成するつもりはありません。もしそうなら、私はおそらくGLUT以外のものを使うでしょう。しかし、私はGLUTを使用しているので、iDevGamesで説明されている問題以外は、この最新の実装でGLUTに十分だと思いますか?私が今考えることができる唯一の本当の問題はglutPostRedisplay()、シーンが何かを変えるたびに電話をかけ続け、再描画する新しいものがなくなるまで電話をかけ続ける必要があるということです。より良い原因のためにコードに少し複雑さが加わったと思います。

どう思いますか?

4

3 に答える 3

1

glutゲームループ。glutMainLoop()を呼び出すと、exit()信号以外の終了条件なしで「forループ」が実行されます。現在行っているようなプログラムを実装できますが、いくつかの小さな変更が必要です。まず、FPSが何であるかを知りたい場合は、更新関数ではなく、renderScene()関数にその追跡を配置する必要があります。当然、更新関数はタイマーで指定された速度で呼び出され、経過時間はフレーム間の時間の尺度として扱われます。一般に、これはglutPostRedisplayの呼び出しがかなり遅く、必要がない場合はglutが画面を更新しようとしないためです(シーンが変更されていない場合は再描画する必要はありません)。ただし、renderSceneが呼び出される場合もあります。例えば、ウィンドウ全体に何かをドラッグした場合。これを行うと、FPSが高くなります(レンダリング機能でFPSを適切に追跡している場合)。

于 2011-03-18T20:51:03.060 に答える
0

ループglutIdleFuncと同様に、可能な限り継続的に呼び出されるを使用できます。while(game_is_running)つまり、他の方法でそのwhileループに入れるロジックが何であれ、のコールバックに入れることができますglutIdleFuncglutTimerFuncリンクした(を使用して)記事のように、自分でティックを追跡することで、使用を回避できますglutGet(GLUT_ELAPSED_TIME)

于 2011-03-18T20:06:07.523 に答える
0

例として、レンダリングフレームレートとは関係なく、固定フレームレートで更新されるマウス駆動の回転行列があります。私のプログラムでは、スペースバーがベンチマークモードを切り替え、ブールfxFPSを決定します。

ドラッグ中にマウスボタンを離すと、このマトリックスによって変換されたオブジェクトを「投げる」ことができます。

fxFPSがtrueの場合、レンダリングフレームレートはアニメーションフレームレートに調整されます。それ以外の場合、アニメーションをトリガーするのに十分なミリ秒が経過していなくても、ベンチマークのために同一のフレームが繰り返し描画されます。

フレームの速度を遅くしたり速くしたりすることを考えている場合は、それぞれの場合にレンダリングフレームとアニメーションフレームのどちらを意味するのかを慎重に検討する必要があります。この例では、単純なアニメーションのレンダリングスロットリングは、アニメーションアクセラレーションと組み合わされています。これは、低速のアニメーションでフレームがドロップされる可能性がある場合に使用します。

アニメーションを高速化するために、回転はループで繰り返し実行されます。このようなループは、適応回転角で三角関数を実行するオプションと比較して遅すぎません。実際に実行に時間がかかるループ内に何を入れるかに注意してください。FPSは低くなります。このループは、それが占めるフレームドロップごとに、完了するのに余分なフレームよりもはるかに少ないので、かなり安全です。

int xSt, ySt, xCr, yCr, msM = 0, msOld = 0;
bool dragging = false, spin = false, moving = false;
glm::mat4 mouseRot(1.0f), continRot(1.0f);
float twoOvHght; // Set in reshape()
glm::mat4 mouseRotate(bool slow) {
    glm::vec3 axis(twoOvHght * (yCr - ySt), twoOvHght * (xCr - xSt), 0); // Perpendicular to mouse motion
    float len = glm::length(axis);
    if (slow) { // Slow rotation; divide angle by mouse-delay in milliseconds; it is multiplied by frame delay to speed it up later
        int msP = msM - msOld;
        len /= (msP != 0 ? msP : 1);
    }
    if (len != 0) axis = glm::normalize(axis); else axis = glm::vec3(0.0f, 0.0f, 1.0f);
    return rotate(axis, cosf(len), sinf(len));
}
void mouseMotion(int x, int y) {
    moving = (xCr != x) | (yCr != y);
    if (dragging & moving) {
        xSt = xCr; xCr = x; ySt = yCr; yCr = y; msOld = msM; msM = glutGet(GLUT_ELAPSED_TIME);
        mouseRot = mouseRotate(false) * mouseRot;
    }
}
void mouseButton(int button, int state, int x, int y) {
    if (button == 0) {
        if (state == 0) {
            dragging = true; moving = false; spin = false;
            xCr = x; yCr = y; msM = glutGet(GLUT_ELAPSED_TIME);
            glutPostRedisplay();
        } else {
            dragging = false; spin = moving;
            if (spin) continRot = mouseRotate(true);
        }
    }
}

そして後で...

bool fxFPS = false;
int T = 0, ms = 0;
const int fDel = 20;
void display() {
    ms = glutGet(GLUT_ELAPSED_TIME);
    if (T <= ms) { T = ms + fDel;
        for (int lp = 0; lp < fDel; lp++) {
            orient = rotY * orient; orientCu = rotX * rotY * orientCu; // Auto-rotate two orientation quaternions
            if (spin) mouseRot = continRot * mouseRot; // Track rotation from thowing action by mouse
        }
        orient1 = glm::mat4_cast(orient); orient2 = glm::mat4_cast(orientCu);
    }
    // Top secret animation code that will make me rich goes here
    glutSwapBuffers();
    if (spin | dragging) { if (fxFPS) while (glutGet(GLUT_ELAPSED_TIME) < T); glutPostRedisplay(); } // Fast, repeated updates of the screen
}

軸の周りに物を投げるのを楽しんでください。ほとんどの人がそうしていることがわかります。fpsは、インターフェイスやレンダリングに何の影響も与えないことに注意してください。除算の使用を最小限に抑えたので、比較は素晴らしく正確である必要があり、時計の不正確さが不必要に蓄積されることはありません。

マルチプレイヤーゲームの同期は、別の18の会話だと私は判断します。

于 2014-11-14T17:41:41.897 に答える