一部の古いゲームが最新のハードウェアで非常に速く実行されるのはなぜですか?


64

90年代初期のWindowsコンピューターを起動して、比較的現代的なコンピューターで実行しようとした古いプログラムがいくつかあります。興味深いことに、彼らは非常に速い速度で走りました-いいえ、毎秒60フレームの種類の高速ではなく、むしろキャラクターのオーマイゴッドは音速の種類の歩行です速い。矢印キーを押すと、キャラクターのスプライトが通常よりもはるかに速く画面上で圧縮されます。ゲームの時間の進行は、予想よりはるかに速く発生していました。これらのゲームを実際にプレイできるように、CPU遅くするプログラムもあります

これは、CPUサイクルなどに依存するゲームに関連していると聞きました。私の質問は:

  • 古いゲームはなぜこれを行うのですか?
  • 新しいゲームはこれを行わ、CPU周波数とは無関係に実行されるのはどうしてですか?

これはしばらく前のことであり、互換性のトリックを行ったことは覚えていませんが、それは重要なことです。これを修正する方法についてはたくさんの情報がありますが、なぜそのように動作するのかについてはあまり情報がありません。それが私が求めていることです。
TreyK

9
古いPCのターボボタンを覚えていますか?:D
ビクトルメルグレン

1
あ。ABC80(ベーシックのスウェーデン語z80ベースのPC)での1秒の遅延を思い出させます。FOR F IN 0 TO 1000; NEXT F;
マッケ

1
ただ明確にするために、「90年代初期のWindowsコンピューターから取り出したいくつかの古いプログラム」は、Windowsマシン上のDOSプログラム、またはこの動作が発生するWindowsプログラムですか?私はDOSで見るのに慣れていますが、ウィンドウズではなく、IIRCです。
そのブラジル人

回答:


52

システムクロックは特定のレートで動作し、内部タイマーはそのクロックレートに関連付けられていると想定していたと思います。これらのゲームのほとんどはおそらくDOSで実行され、リアルモード(完全なハードウェアアクセスを直接使用)であり、PC用のiirc 4.77 MHzシステムおよびAmigaなどの他のシステムで実行された標準プロセッサを実行していると想定していました。

また、プログラム内に内部タイミングループを記述しないことでリソースを少し節約するなど、これらの仮定に基づいて巧妙なショートカットを作成しました。また、できるだけ多くのプロセッサパワーを使用しました。これは、低速で、多くの場合受動的に冷却されるチップの時代には適切なアイデアでした。

異なるプロセッサ速度を回避する最初の方法の1つは、古くなったTurboボタン(システムの速度を低下させる)でした。現代のアプリケーションは、プロテクトモードであり、OSは、リソースを管理する傾向がある-彼らはないでしょう許可(とにかく32ビットシステム上でNTVDMで実行されている)DOSアプリケーションは多くの場合、プロセッサのすべてを使用します。要するに、OSはAPIと同様に賢くなりました。

このガイドは、ロジックとメモリが私を失敗させたOldskool PCに大きく基づいています。これは素晴らしい読み物であり、おそらく「理由」をより深く掘り下げています。

CPUkillerのようなものは、システムを「遅く」するために可能な限り多くのリソースを使い果たしますが、これは非効率的です。DOSBoxを使用して、アプリケーションに表示されるクロック速度を管理した方が良いでしょう。


14
これらのゲームの中には、何も想定していないものもありました。可能な限り高速で実行され、それらのCPUで「再生可能」でした;-)
Jan Doggen

2
情報については。「新しいゲームはこれを行わず、CPU周波数とは無関係に実行されるのはどうしてですか?」gamedev.stackexchange.comでのようなものを検索してみてくださいgame loop。基本的に2つの方法があります。1)できるだけ速く実行し、ゲームの実行速度に基づいて移動速度などを調整します。2)sleep()次の「ティック」の準備ができるまで、速すぎる場合()待ちます。
ジョージダケット

24

Journeyman Geekの回答(私の編集が拒否されたため)に加えて、コーディングパート/開発者の視点に興味のある人々のために:

プログラマーの観点からは、興味のある人にとっては、DOS時間はすべてのCPUティックが重要であったため、プログラマーはコードを可能な限り高速に保ちました。

プログラムが最大CPU速度で実行される典型的なシナリオは、次の単純なものです(擬似C):

int main()
{
    while(true)
    {

    }
}

これは永遠に実行されます。今、このコードスニペットを擬似DOSゲームに変えましょう。

int main()
{
    bool GameRunning = true;
    while(GameRunning)
    {
        ProcessUserMouseAndKeyboardInput();
        ProcessGamePhysics();
        DrawGameOnScreen();

        //close game
        if(Pressed(KEY_ESCAPE))
        {
            GameRunning = false;
        }
    }
}

DrawGameOnScreen関数がダブルバッファリング/ V-sync(DOSゲームが作成された当時は高額だった)を使用しない限り、ゲームは最大CPU速度で実行されます。現代のモバイルi7では、これは1秒あたり約1,000,000〜5,000,000回実行されます(ラップトップの構成と現在のCPU使用量に依存します)。

これは、64ビットウィンドウで最新のCPUでDOSゲームを動作させることができれば、物理処理が「想定」されている場合、人間がプレイするには速すぎる1000(1000!)FPSを取得できることを意味します。 50-60 fpsの間。

今日の開発者ができることは:

  1. ゲームでV-Syncを有効にします(*ウィンドウアプリケーションでは使用できません** [別名フルスクリーンアプリでのみ使用可能])
  2. 最後の更新間の時間差を測定し、時間差に従って物理学を更新して、FPSレートに関係なくゲーム/プログラムを同じ速度で効果的に実行します
  3. プログラムでフレームレートを制限する

***グラフィックカード/ドライバー/ OSの構成によっては、可能があります。

ポイント1については、実際には「プログラミング」ではないため、例を示しません。グラフィック機能を使用しているだけです。

ポイント2と3については、対応するコードスニペットと説明を示します。

2:

int main()
{
    bool GameRunning = true;
    long long LastTick = GetCurrentTime();
    long long TimeDifference;
    while(GameRunning)
    {
        TimeDifference = GetCurrentTime()-LastTick;
        LastTick = GetCurrentTime();

        //process movement based on how many time passed and which keys are pressed
        ProcessUserMouseAndKeyboardInput(TimeDifference);

        //pass the time difference to the physics engine so it can calculate anything time-based
        ProcessGamePhysics(TimeDifference);

        DrawGameOnScreen();

        //close game if escape is pressed
        if(Pressed(KEY_ESCAPE))
        {
            GameRunning = false;
        }
    }
}

ここでは、ユーザー入力と物理学が時間差を考慮に入れているのを見ることができますが、ループが可能な限り高速で実行されているため、画面上で1000+ FPSを取得できます。物理エンジンはどれだけ時間が経過したかを知っているため、「仮定なし」や「特定のフレームレート」に依存する必要はなく、ゲームはどのCPUでも同じ速度で動作します。

3:

フレームレートをたとえば30 FPSに制限するために開発者ができることは、実際にはそれほど難しくありません。

int main()
{
    bool GameRunning = true;
    long long LastTick = GetCurrentTime();
    long long TimeDifference;

    double FPS_WE_WANT = 30;
    //how many milliseconds need to pass before we need to draw again so we get the framerate we want?
    double TimeToPassBeforeNextDraw = 1000.0/FPS_WE_WANT;
    //For the geek programmers: note, this is pseudo code so I don't care for variable types and return types..
    double LastDraw = GetCurrentTime();

    while(GameRunning)
    {
        TimeDifference = GetCurrentTime()-LastTick;
        LastTick = GetCurrentTime();

        //process movement based on how many time passed and which keys are pressed
        ProcessUserMouseAndKeyboardInput(TimeDifference);

        //pass the time difference to the physics engine so it can calculate anything time-based
        ProcessGamePhysics(TimeDifference);

        //if certain amount of milliseconds pass...
        if(LastTick-LastDraw >= TimeToPassBeforeNextDraw)
        {
            //draw our game
            DrawGameOnScreen();

            //and save when we last drawn the game
            LastDraw = LastTick;
        }

        //close game if escape is pressed
        if(Pressed(KEY_ESCAPE))
        {
            GameRunning = false;
        }
    }
}

ここで何が起こるかは、プログラムが何ミリ秒経過したかをカウントし、特定の量(33ミリ秒)に達すると、ゲーム画面を再描画し、実質的に〜30近くのフレームレートを適用することです。

また、開発者によっては、上記のコードをわずかに変更して、すべての処理を30 fpsに制限することもできます。

int main()
{
    bool GameRunning = true;
    long long LastTick = GetCurrentTime();
    long long TimeDifference;

    double FPS_WE_WANT = 30;
    //how many miliseconds need to pass before we need to draw again so we get the framerate we want?
    double TimeToPassBeforeNextDraw = 1000.0/FPS_WE_WANT;
    //For the geek programmers: note, this is pseudo code so I don't care for variable types and return types..
    double LastDraw = GetCurrentTime();

    while(GameRunning)
    {

        LastTick = GetCurrentTime();
        TimeDifference = LastTick-LastDraw;

        //if certain amount of miliseconds pass...
        if(TimeDifference >= TimeToPassBeforeNextDraw)
        {
            //process movement based on how many time passed and which keys are pressed
            ProcessUserMouseAndKeyboardInput(TimeDifference);

            //pass the time difference to the physics engine so it can calculate anything time-based
            ProcessGamePhysics(TimeDifference);


            //draw our game
            DrawGameOnScreen();

            //and save when we last drawn the game
            LastDraw = LastTick;

            //close game if escape is pressed
            if(Pressed(KEY_ESCAPE))
            {
                GameRunning = false;
            }
        }
    }
}

他にもいくつかの方法があり、そのうちのいくつかは本当に嫌いです。

たとえば、を使用しsleep(<amount of milliseconds>)ます。

これはフレームレートを制限する方法の1つですが、ゲームの処理に3ミリ秒以上かかるとどうなりますか?そして、スリープを実行します...

これにより、sleep()原因となるはずのフレームレートよりもフレームレートが低くなります。

たとえば、スリープ時間を16ミリ秒とします。これにより、プログラムは60 Hzで実行されます。データ、入力、描画、その他すべての処理に5ミリ秒かかります。1ループで21ミリ秒になり、50 Hzをわずかに下回る結果になりますが、60 Hzのままになることは簡単ですが、スリープのために不可能です。

1つの解決策は、処理時間を測定し、必要な睡眠から処理時間を差し引くという形で適応スリープを作成し、「バグ」を修正することです。

int main()
{
    bool GameRunning = true;
    long long LastTick = GetCurrentTime();
    long long TimeDifference;
    long long NeededSleep;

    while(GameRunning)
    {
        TimeDifference = GetCurrentTime()-LastTick;
        LastTick = GetCurrentTime();

        //process movement based on how many time passed and which keys are pressed
        ProcessUserMouseAndKeyboardInput(TimeDifference);

        //pass the time difference to the physics engine so it can calculate anything time-based
        ProcessGamePhysics(TimeDifference);


        //draw our game
        DrawGameOnScreen();

        //close game if escape is pressed
        if(Pressed(KEY_ESCAPE))
        {
            GameRunning = false;
        }

        NeededSleep = 33 - (GetCurrentTime()-LastTick);
        if(NeededSleep > 0)
        {
            Sleep(NeededSleep);
        }
    }
}

16

主な原因の1つは、プログラムの起動時に調整される遅延ループの使用です。既知の時間内にループが実行される回数をカウントし、それを分割して小さな遅延を生成します。次に、これを使用してsleep()関数を実装し、ゲームの実行速度を調整できます。このカウンターが最大になると、ループでプロセッサーが非常に高速になり、小さな遅延が非常に小さくなります。さらに、最新のプロセッサは負荷に基づいて速度を変更しますが、場合によってはコアごとであっても、遅延がさらに大きくなります。

本当に古いPCゲームの場合、彼らはゲームのペースを調整しようとしても、できるだけ速く走りました。これはIBM PC XTの時代によく見られましたが、この理由でターボボタンが存在し、システムを4.77mhzプロセッサに合わせるために速度が低下していました。

DirectXのような最新のゲームとライブラリは、高歳差タイマーにアクセスできるため、調整されたコードベースの遅延ループを使用する必要はありません。


4

最初のすべてのPCは最初は同じ速度で実行されていたため、速度の違いを考慮する必要はありませんでした。

また、最初の多くのゲームではCPU負荷がかなり固定されていたため、一部のフレームが他のフレームよりも速く実行されることはほとんどありませんでした。

現在、ヤーキッズとヤーファンシーFPSシューターでは、1秒間床を見ることができ、次のグランドキャニオンでは、負荷変動がより頻繁に発生します。:)

(また、ゲームを常に60 fpsで実行できるほど高速なハードウェアコンソールはほとんどありません。これは主に、コンソール開発者が30 Hzを選択し、ピクセルを2倍輝くようにしているためです...)

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.