我有一些旧程序,我从 90 年代早期的 Windows 计算机上拉下来,并试图在相对现代的计算机上运行它们。有趣的是,他们以极快的速度奔跑——不,不是每秒 60 帧的快,而是天哪,角色正在以声音的速度行走快速地。我会按一个箭头键,角色的精灵会比正常情况更快地在屏幕上滑动。游戏中的时间进程比它应该发生的要快得多。甚至还有一些程序可以降低 CPU 的速度,从而使这些游戏真正可以玩。
我听说这与游戏有关,取决于 CPU 周期,或类似的东西。我的问题是:
Jou*_*eek 54
我相信他们假设系统时钟将以特定速率运行,并将其内部计时器绑定到该时钟速率。这些游戏中的大多数可能在 DOS 上运行,并且是实模式(具有完整的、直接的硬件访问),并假设您运行的是用于 PC的iirc 4.77 MHz 系统以及该模型为其他系统(如 Amiga)运行的任何标准处理器。
他们还基于这些假设采取了巧妙的捷径,包括通过不在程序中编写内部时序循环来节省一点资源。它们还尽可能多地占用了处理器能力——这在速度缓慢、通常被动冷却的芯片时代是一个不错的主意!
最初解决不同处理器速度的一种方法是使用旧的Turbo 按钮(它会减慢您的系统速度)。现代应用程序处于保护模式,操作系统倾向于管理资源——在许多情况下,它们不允许DOS 应用程序(无论如何都在 32 位系统上的 NTVDM 中运行)用尽所有处理器。简而言之,操作系统变得更加智能,API 也是如此。
大量基于Oldskool PC 上的本指南,其中逻辑和内存让我失望——这是一本很好的读物,可能更深入地探讨了“为什么”。
像CPUkiller这样的东西会尽可能多地消耗资源来“减慢”系统的速度,这是低效的。您最好使用DOSBox来管理您的应用程序看到的时钟速度。
Giz*_*zmo 26
作为对 Journeyman Geek 的回答(因为我的编辑被拒绝)的补充,供对编码部分/开发人员观点感兴趣的人使用:
从程序员的角度来看,对于那些感兴趣的人来说,DOS 时代是每个 CPU 滴答都很重要的时期,因此程序员尽可能快地保存代码。
任何程序都以最大 CPU 速度运行的典型场景是这个简单的伪 C:
int main()
{
while(true)
{
}
}
Run Code Online (Sandbox Code Playgroud)
这将永远运行。
现在,让我们把这个代码片段变成一个伪 DOS 游戏:
int main()
{
bool GameRunning = true;
while(GameRunning)
{
ProcessUserMouseAndKeyboardInput();
ProcessGamePhysics();
DrawGameOnScreen();
// close game
if(Pressed(KEY_ESCAPE))
{
GameRunning = false;
}
}
}
Run Code Online (Sandbox Code Playgroud)
除非该DrawGameOnScreen功能使用双缓冲/垂直同步(这在制作 DOS 游戏的年代有点昂贵),否则游戏将以最大 CPU 速度运行。在现代移动 i7 上,这将以每秒 1,000,000 到 5,000,000 次的速度运行(取决于笔记本电脑的配置和当前的 CPU 使用率)。
这意味着,如果我可以在我的 64 位 Windows 的现代 CPU 上运行任何 DOS 游戏,我可以获得超过一千(1000!)FPS——这对于任何人来说都太快了——如果物理处理“假设”它在 50 到 60 FPS 之间运行。
*取决于图形卡/驱动器/ OS结构,可以是可能的。
对于第一个选项,我不会展示任何示例,因为它并不是真正的“编程”——它只是使用图形功能。
至于另外两个选项,我会展示相应的代码片段和解释。
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;
}
}
}
Run Code Online (Sandbox Code Playgroud)
在这里你可以看到用户输入和物理考虑了时间差,但你仍然可以在屏幕上获得 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;
}
}
}
Run Code Online (Sandbox Code Playgroud)
这里发生的是程序计算经过的毫秒数,当达到一定数量(33 毫秒)时,它会重新绘制游戏屏幕,有效地应用接近 30 FPS 的帧速率。
另外,开发者可以通过稍微修改上面的代码来选择将所有处理限制为 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;
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
还有其他一些方法,其中一些我真的很讨厌。例如,使用sleep(NumberOfMilliseconds).
我知道这是限制帧率的一种方法,但是当您的游戏处理需要 3 毫秒或更长时间然后您执行睡眠时会发生什么?这将导致帧率低于sleep()应该导致的帧率。
例如,假设睡眠时间为 16 毫秒。这将使程序以 60 Hz 的频率运行。现在让我们假设数据、输入、绘图和所有东西的处理需要 5 毫秒。这使我们在一个循环中达到 21 毫秒,这导致略低于 50 Hz,而您仍然可以轻松地保持在 60 Hz,但由于硬编码睡眠,这是不可能的。
一种解决方案是进行“自适应睡眠”,以测量处理时间并从所需睡眠中扣除处理时间的形式,从而修复我们的“错误”:
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);
}
}
}
Run Code Online (Sandbox Code Playgroud)
Bri*_*ian 16
一个主要原因是使用了在程序启动时校准的延迟循环。他们计算循环在已知时间内执行的次数,并将其划分以产生更小的延迟。然后可以使用它来实现 sleep() 函数来调整游戏的执行速度。由于处理器在循环中速度太快以至于小延迟最终变得太小,因此当这个计数器达到最大值时就会出现问题。此外,现代处理器根据负载改变速度,有时甚至是基于每个核心,这使得延迟更大。
对于非常老的 PC 游戏,他们只是尽可能快地运行,而不考虑游戏的节奏。在 IBM PC XT 时代更是如此,但是由于这个原因,存在一个涡轮按钮会降低系统速度以匹配 4.77mhz 处理器。
现代游戏和像 DirectX 这样的库可以访问高速进动计时器,因此不需要使用基于校准代码的延迟循环。
所有第一台 PC 一开始都以相同的速度运行,因此无需考虑速度差异。
此外,许多游戏一开始就有相当固定的 CPU 负载,因此某些帧不太可能比其他帧运行得更快。
如今,随着您的孩子和精美的 FPS 射击游戏,您可以前一秒看地面,下一秒就看大峡谷,负载变化更频繁地发生。:)
(而且,很少有硬件控制台能够足够快地以 60 fps 持续运行游戏。这主要是因为控制台开发人员选择 30 Hz 并使像素闪亮两倍......)