90年代前半のWindowsコンピューターを引っ張って、比較的最近のコンピューターで実行しようとした古いプログラムがいくつかあります。興味深いことに、彼らは非常に速い速度で走っていました-いいえ、毎秒60フレームではなく、ああ、私のキャラクターのキャラクターは、音のスピードで歩いています。速い。矢印キーを押すと、キャラクターのスプライトが通常よりもはるかに速く画面をジップします。ゲームの時間進行は、予想よりもはるかに速く発生していました。これらのゲームを実際にプレイできるように、 CPUをスローダウン するプログラムも作成されています。
これはCPUサイクルに依存するゲームなどに関係していると聞いています。私の質問は:
私は、システムクロックが特定のレートで動作すると想定し、内部タイマーをそのクロックレートに関連付けていると考えています。これらのゲームのほとんどはおそらくDOS上で実行され、 リアルモード (完全な直接ハードウェアアクセス)であり、iirc 4.77 MHzシステムをPCおよびその他の標準で実行していると想定しています。このモデルのプロセッサは、Amigaなどの他のシステムで実行されました。
また、プログラム内に内部タイミングループを記述しないことにより、リソースのごく一部を節約するなど、これらの前提に基づいて巧妙なショートカットを採用しました。また、可能な限り多くのプロセッサ能力を消費しました。これは、低速で、しばしば受動的に冷却されるチップの時代には、まともなアイデアでした!
当初、異なるプロセッサ速度を回避する1つの方法は、古き良き ターボボタン (システムの速度を低下させる)でした。最新のアプリケーションはプロテクトモードであり、OSはリソースを管理する傾向があります。つまり、すべてのプロセッサを使い切ることはできませんallow DOSアプリケーション(32ビットシステムのNTVDMで実行されています)。多くの場合。つまり、APIと同様に、OSも賢くなっています。
重く基づいている Oldskool PCのこのガイド ロジックとメモリが私を失敗させた場所-これは素晴らしい読み物であり、おそらく「理由」をより深く掘り下げています。
CPUkiller のようなものは、システムを「スローダウン」するために、可能な限り多くのリソースを使用しますが、これは非効率的です。 DOSBox を使用して、アプリケーションが認識するクロック速度を管理することをお勧めします。
コーディングパート/開発者の観点に関心のある人のための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ビットWindowsで最新のCPUでDOSゲームを動作させることができれば、1000(1000!)を超えるFPSが得られる可能性があることを意味します。 50〜60 FPSで実行されることを「想定」します。
*グラフィックスカード/ドライバー/ OS構成によっては、mayが可能です。
最初のオプションについては、実際には「プログラミング」ではないため、例を示しません。グラフィック機能を使用するだけです。
他の2つのオプションについては、対応するコードスニペットと説明を表示します。
_int main()
{
bool GameRunning = true;
long long LastTick = GetCurrentTime();
long long TimeDifference;
while(GameRunning)
{
TimeDifference = GetCurrentTime() - LastTick;
LastTick = GetCurrentTime();
// process movements based on time passed and keys 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でも同じフレームレートで動作します。
たとえば、フレームレートを30 FPSに制限するために開発者ができることは、これ以上難しくありません。
_int main()
{
bool GameRunning = true;
long long LastTick = GetCurrentTime();
long long TimeDifference;
double DESIRED_FPS = 30;
// how many milliseconds need to pass before the next draw so we get the framerate we want
double TimeToPassBeforeNextDraw = 1000.0/DESIRED_FPS;
// note to geek programmers: this is pseudo code, so I don't care about variable types and return types
double LastDraw = GetCurrentTime();
while(GameRunning)
{
TimeDifference = GetCurrentTime() - LastTick;
LastTick = GetCurrentTime();
// process movements based on time passed and keys pressed
ProcessUserMouseAndKeyboardInput(TimeDifference);
// pass the time difference to the physics engine, so it can calculate anything time-based
ProcessGamePhysics(TimeDifference);
// if certain number of milliseconds pass...
if(LastTick-LastDraw >= TimeToPassBeforeNextDraw)
{
// draw our game
DrawGameOnScreen();
// and save when we last drew the game
LastDraw = LastTick;
}
// close game if escape is pressed
if(Pressed(KEY_ESCAPE))
{
GameRunning = false;
}
}
}
_
ここで何が起こるかというと、プログラムは渡されたミリ秒をカウントし、一定の量(33 ms)に達すると、ゲーム画面を再描画し、実質的に30 FPSに近いフレームレートを適用します。
また、開発者は、上記のコードをわずかに変更することでall処理を30 FPSに制限することを選択できます。
_int main()
{
bool GameRunning = true;
long long LastTick = GetCurrentTime();
long long TimeDifference;
double DESIRED_FPS = 30;
// how many milliseconds need to pass before the next draw so we get the framerate we want
double TimeToPassBeforeNextDraw = 1000.0/DESIRED_FPS;
// note to geek programmers: this is pseudo code, so I don't care about variable types and return types
double LastDraw = GetCurrentTime();
while(GameRunning)
{
LastTick = GetCurrentTime();
TimeDifference = LastTick - LastDraw;
// if certain number of milliseconds pass...
if(TimeDifference >= TimeToPassBeforeNextDraw)
{
// process movements based on time passed and keys 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 drew the game
LastDraw = LastTick;
// close game if escape is pressed
if(Pressed(KEY_ESCAPE))
{
GameRunning = false;
}
}
}
}
_
他にもいくつかの方法があり、そのうちのいくつかは本当に嫌いです。たとえば、sleep(NumberOfMilliseconds)
を使用します。
これがフレームレートを制限する方法の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 movements based on time passed and keys 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);
}
}
}
_
主な原因の1つは、プログラムの起動時に調整される遅延ループの使用です。既知の時間内にループが実行される回数をカウントし、分割して小さな遅延を生成します。その後、これを使用してsleep()関数を実装し、ゲームの実行のペースを調整できます。このカウンタが最大になると、ループ上のプロセッサが非常に高速になり、小さな遅延が非常に小さくなり、問題が発生します。さらに、最新のプロセッサーは負荷に基づいて速度を変更し、コアごとに変更することもあるので、遅延がさらに解消されます。
本当に古いPCゲームの場合は、ゲームのペースを調整しようとすることを考慮せずに、できるだけ速く実行しました。これは、IBM PC XT日ではより当てはまりますが、ターボボタンが存在していて、この理由でシステムが4.77MHzプロセッサに一致するのを遅らせました。
DirectXのような最新のゲームやライブラリは高精度のタイマーにアクセスできるため、調整済みコードベースの遅延ループを使用する必要はありません。
最初のすべてのPCは最初は同じ速度で動作していたため、速度の違いを考慮する必要はありませんでした。
また、最初の多くのゲームにはかなり固定されたCPU負荷があったため、一部のフレームが他のフレームよりも速く実行されることはほとんどありませんでした。
今日では、子供たちと派手なFPSシューティングゲームを使用して、床を1秒間見ることができ、グランドキャニオンでは次の段階で、負荷変動がより頻繁に発生します。 :)
(そして、いくつかのハードウェアコンソールはゲームを60 fpsで絶えず実行するのに十分な速度です。これは主に、コンソール開発者が30 Hzを選択し、ピクセルを2倍の光沢にすることによるものです...)