4

Sdl ゲームを作成しています。これは 2D シューティング ゲームです。SDL を使用してサーフェスをインポートし、OpenGL を使用して画面に描画しています (SDL よりも高速に動作するためです)。2 つのスレッドを実行しています。1 つは処理とレンダリング用で、もう 1 つは入力用です。基本的に、処理はCPUの1〜2%を占めていますが、入力ループは25%を占めています(クアッドコアでは1つのフルコアです). それぞれの前に SDL_Delay(1) を実行してみましたが、うまくいきましwhile (SDL_PollEvent(&keyevent))た! プロセス全体の CPU 負荷を 3% に減らします。ただし、厄介な副作用があります。プログラム全体の入力はハンディキャップです: 押されたすべてのキーを検出するわけではありません.たとえば、キャラクターを動かすために、キーボードを叩いて反応するのに最大3秒かかる場合があります.

SDL_PeepEvent()また、 andを使用して解決しようとしましたSDL_WaitEvent()が、同じ (非常に長い!) 遅延が発生しています。

イベント ループ コード:

void GameWorld::Movement()
{
    SDL_Event keyevent;
    bool x1, x2, y1, y2, z1, z2, z3, m;         // Booleans to determine the
    x1 = x2 = y1 = y2 = z1 = z2 = z3 = m = 0;   // movement direction
    SDL_EnableKeyRepeat(0, 0);
    while (1)
    {
        while (SDL_PollEvent(&keyevent))
        {
            switch(keyevent.type)
            {
            case SDL_KEYDOWN:
                switch(keyevent.key.keysym.sym)
                {
                case SDLK_LEFT:
                    x1 = 1;
                    x2 = 0;
                    break;
                case SDLK_RIGHT:
                    x1 = 0;
                    x2 = 1;
                    break;
                case SDLK_UP:
                    y1 = 1;
                    y2 = 0;
                    break;
                case SDLK_DOWN:
                    y1 = 0;
                    y2 = 1;
                    break;
                default:
                    break;
                }
                break;
            case SDL_KEYUP:
                switch(keyevent.key.keysym.sym)
                {
                case SDLK_LEFT:
                    x1 = x2 = 0;
                    break;
                case SDLK_RIGHT:
                    x1 = x2 = 0;
                    break;
                case SDLK_UP:
                    y1 = y2 = 0;
                    break;
                case SDLK_DOWN:
                    y1 = y2 = 0;
                    break;
                default:
                    break;
                }
                break;
            case SDL_QUIT:
                PrintToFile("The game was closed manually.\n");
                CleanUp();
                return;
                break;
            default:
                break;
            }
        }
        m = x1 || x2 || y1 || y2;
        if (m)   // if any button is pushed down, calculate the movement
        {        // direction and assign it to the player
            z1 = (x1 || x2) && (y1 || y2);
            z2 = !x1 && (x2 || y2);
            z3 = (!y1 && x1) || (!y2 && x2);
            MainSurvivor->SetMovementDirection(4 * z1 + 2 * z2 + z3);
        }
        else    // if no button is pushed down, reset the direction
            MainSurvivor->SetMovementDirection(-1);
    }
}

計算/レンダリング ループのコード:

void GameWorld::GenerateCycles()
{
    int Iterator = 0;
    time_t start;   
    SDL_Event event;

    Render();
    _beginthread(MovementThread, 0, this);
    while (1)
    {
            // I know I check this in input loop, but if I comment
        SDL_PollEvent(&event);   // out it from here, that loop cannot
        if (event.type == SDL_QUIT)  // see any of the events (???)!
        {
            PrintToFile("The game was closed manually.\n");
            CleanUp();
        }                            // It never closes through here though

        start = clock();
        Iterator++;
        if (Iterator >= 232792560)
            Iterator %= 232792560;
        MainSurvivor->MyTurn(Iterator);
        for (unsigned int i = 0; i < Survivors.size(); i++)
        {
            Survivors[i]->MyTurn(Iterator);
            if (Survivors[i]->GetDiedAt() != 0 && Survivors[i]->GetDiedAt() + 25 < clock())
            {
                delete Survivors[i];
                Survivors.erase(Survivors.begin() + 5);
            }
        }
        if (Survivors.size() == 0)
            SpawnSurvivors();

        for (int i = 0; i < int(Zombies.size()); i++)
        {
            Zombies[i]->MyTurn(Iterator);
            if (Zombies[i]->GetType() == 3 && Zombies[i]->GetDiedAt() + 25 < Iterator)
            {
                delete Zombies[i];
                Zombies.erase(Zombies.begin() + i);
                i--;
            }
        }
        if (Zombies.size() < 3)
            SpawnZombies();

            // No need to render every cycle, gameplay is slow
        if (Iterator % 2 == 0)
            Render();

        if (Interval - clock() + start > 0)
            SDL_Delay(Interval - clock() + int(start));
    }
}

誰にもアイデアはありますか?

4

4 に答える 4

5

私はSDLやゲームプログラミングの経験はあまりありませんが、ランダムなアイデアをいくつか紹介します。

状態変化に反応する

あなたのコード:

while (1)
{
    while (SDL_PollEvent(&keyevent))
    {
        switch(keyevent.type)
        {
            // code to set keyboard state
        }
    }

    // code to calculate movement according to keyboard state
    // then act on that movement
}

これは、キーボードで何も起きていなくても、データを計算して設定していることを意味します。

データの設定に費用がかかる場合(ヒント:同期データ)、さらに費用がかかります。

SDL_WaitEvent:状態の測定

1つのプロセッサを100%使用するために作成した回転ではなく、イベントが発生するのを待つ必要があります。

これは、自宅でのテスト用に作成したイベントループのバリエーションです。

while(true)
{
    // message processing loop
    ::SDL_Event event ;

    ::SDL_WaitEvent(&event) ; // THIS IS WHAT IS MISSING IN YOUR CODE

    do
    {
        switch (event.type)
        {
            // etc.
        }
    }
    while(::SDL_PollEvent(&event)) ;

    // re-draw the internal buffer
    if(this->m_isRedrawingRequired || this->m_isRedrawingForcedRequired)
    {
        // redrawing code
    }

    this->m_isRedrawingRequired = false ;
    this->m_isRedrawingForcedRequired = false ;
}

注:これはシングルスレッドでした。スレッドについては後で話します。

注2:2つの「m_isRedrawing ...」ブール値のポイントは、これらのブール値の1つが真の場合、およびタイマーが質問した場合に、強制的に再描画することです。通常、再描画はありません。

私のコードとあなたのコードの違いは、スレッドを「待たせる」ことは決してないということです。

キーボードイベント

キーボードイベントの処理に問題があると思います。

あなたのコード:

        case SDL_KEYDOWN:
            switch(keyevent.key.keysym.sym)
            {
            case SDLK_LEFT:
                x1 = 1;
                x2 = 0;
                break;
            case SDLK_RIGHT:
                x1 = 0;
                x2 = 1;
                break;
            // etc.
            }
        case SDL_KEYUP:
            switch(keyevent.key.keysym.sym)
            {
            case SDLK_LEFT:
                x1 = x2 = 0;
                break;
            case SDLK_RIGHT:
                x1 = x2 = 0;
                break;
            // etc.
            }

たとえば、[左]、[右]、[左]の順に押します。私が期待するのは:

  1. 左を押す:文字が左に移動します
  2. 右を押す:文字が停止します(左と右の両方が押されると)
  3. LEFTを押す:RIGHTがまだ押されているため、文字は右に移動します

あなたの場合、あなたは持っています:

  1. 左を押す:文字が左に移動します
  2. RIGHTを押す:文字が右に移動します(x1 = 0の場合、LEFTは無視されます)
  3. LEFTを押す:RIGHTがまだ押されているにもかかわらず、文字が停止します(x1とx2の両方の設定を解除したため)。

あなたはそれを間違っています、なぜなら:

  1. タイマーを使用してnミリ秒ごとに状況に反応するのではなく、イベントに即座に反応します
  2. あなたは一緒にイベントを混ぜています。

リンクは後で見つけますが、実行する必要があるのは、押されたキーのブール状態の配列を用意することです。何かのようなもの:

// C++ specialized vector<bool> is silly, but...
std::vector<bool> m_aKeyIsPressed ;

使用可能なキーのサイズで初期化します。

m_aKeyIsPressed(SDLK_LAST, false)

次に、キーアップイベントで:

void MyContext::onKeyUp(const SDL_KeyboardEvent & p_oEvent)
{
    this->m_aKeyIsPressed[p_oEvent.keysym.sym] = false ;
}

キーダウン時:

void MyContext::onKeyDown(const SDL_KeyboardEvent & p_oEvent)
{
    this->m_aKeyIsPressed[p_oEvent.keysym.sym] = true ;
}

このように、定期的にチェックするとき(そしてパーツをチェックするときが重要です)、キーボードの正確な瞬間の状態を知り、それに反応することができます。

スレッド

スレッドはかっこいいですが、それなら、あなたは自分が何を扱っているのかを正確に知る必要があります。

たとえば、イベントループスレッドは次のメソッドを呼び出します。

MainSurvivor->SetMovementDirection

解決(レンダリング)スレッドは、次のメソッドを呼び出します。

MainSurvivor->MyTurn(Iterator);

真剣に、2つの異なるスレッド間でデータを共有していますか?

あなたがいる場合(そして私はあなたがそうであることを知っています)、あなたは次のいずれかを持っています:

  1. アクセスを同期しなかった場合、プロセッサキャッシュが原因でデータの一貫性に問題が発生します。簡単に言えば、一方のスレッドによって設定された1つのデータが、妥当な時間内にもう一方のスレッドによって「変更された」と見なされるという保証はありません。
  2. アクセスを(ミューテックス、アトミック変数などで)同期した場合、(たとえば)ループの反復ごとにスレッドごとに少なくとも1回ミューテックスをロック/ロック解除しているため、パフォーマンスが低下します。

代わりに、あるスレッドから別のスレッドに変更を伝達します(たとえば、同期されたキューへのメッセージを介して)。

とにかく、スレッド化は大きな問題なので、SDLやOpenGLと混合する前に、この概念をよく理解しておく必要があります。Herb Sutterのブログは、スレッドに関するすばらしい記事のコレクションです。

あなたがすべきことは:

  1. イベント、投稿されたメッセージ、タイマーを使用して、1つのスレッドで物事を書いてみてください。
  2. パフォーマンスの問題を見つけた場合は、イベントまたは描画スレッドを別の場所に移動しますが、引き続きイベント、投稿されたメッセージ、およびタイマーを使用して通信します

PS:ブール値の何が問題になっていますか?

あなたは明らかにC++(例えば)を使用しているので、またはvoid GameWorld::Movement()の代わりに1または0を使用すると、コードがより明確または高速になりません。truefalse

于 2012-03-23T20:59:37.837 に答える
1

GameWorld::GenerateCycles()のスレッドでSDL を初期化しMovementThreadて呼び出しGameWorld::Movement()いる場合は、次のような問題があります

  • 別のスレッドから SDL ビデオ/イベント関数を呼び出さないでください
于 2012-03-23T20:09:54.767 に答える
0

usleep(50000)の代わりにのようなものを使ってみましたdelay(1)か?

これにより、キューのポーリングの間にスレッドが50ミリ秒スリープするか、同等に、1秒間に20回キューをチェックします。

また、これはどのプラットフォーム上にありますか:Linux、Windows?

Windowsでは、がない場合がありますが、次のようにusleep()試すことができます。select()

struct timeval tv;
tv.tv_sec = 0;
tv.tv_usec = 50000;
select(0, NULL, NULL, NULL, &tv);

もう1つの提案は、イベントの戻りが停止するまで、タイトなループでポーリングを試みることです。保留中のイベントがなくなったら、イベントが再び返され始めるまで、ポーリングの間に50ミリ秒スリープし続けます。

于 2012-03-23T19:40:55.007 に答える
0

SDL_EventFilter と関連する関数を調べることをお勧めします。これはポーリング キューの入力メソッドではないため、ストールは必要ありませんが、私の記憶が正しければ、メイン スレッドでは発生しません。これは、まさにパフォーマンスに必要なものかもしれませんが、コードが複雑になる可能性があります。

于 2012-03-23T20:05:55.780 に答える