SDL2 游戏开发之时间线和帧率
- W_Z_C
- 共 1407 字,阅读约 4 分钟
每个游戏基本上都包含自己独有的时间计算方式,从简单扫雷到文明那种大型的策略游戏。有些时间是显示的,例如扫雷中的计时器,而有些是隐性的,例如 FPS 射击游戏,但不管怎样,你应该都或多或少的感觉到时间的流逝,并且很容易注意到游戏中的时间和现实中的时间一般是不匹配的,例如模拟人生中的小人,几个小时就能让你的小人经历从出生到死亡的整个人生,或许这也算是一种游戏中所独有的特殊体验吧。
游戏中的时间轴
当然今天我们并不准备讨论时间的哲学概念,而是想研究研究游戏中的时间是怎样计算。要想计算游戏中的时间,首先我们要有一个基准时间,只有这样才有参考的依据,这里可以直接选用现实中的时间作为参考。虽然有了参考,但是我们仍然无法度量我们游戏中消耗的时间,因为我们无法在游戏中得知现实中已经过去了多久,这显然是一个需要解决的问题,如果无法解决这个问题,我们是无法将现实中的时间和计算机中的时间进行准确换算的。
其实这个问题,早就已经被解决了,因为计算机硬件本身是包含时钟的,我们可以直接使用 CPU 的高分辨率计时寄存器来度量时间,这种时间是可以和现实中的时间相互转换的,我们可以将他们称之为真实时间轴。
这东西是怎么计算的,又是怎么和现实中的时间换算的呢?
在 Windows 中,有一个 API 接口叫做 QueryPerformanceCounter ,这个函数的作用就是获取当前 CPU 从开机时刻起到执行这个函数之间所经历的时间间隔,这个时间间隔并不是真正现实中的时间单位,而是一个“嘀嗒”计数。最重要的是我们可以用另一个 API 接口 QueryPerformanceFrequency 来获取 1 秒钟的“嘀嗒”个数,这样用总的嘀嗒时间 除以 1秒钟的嘀嗒个数,就可以算出从开机开始 CPU 经过的现实时间。
用代码描述如下:
LARGE_INTEGER nFrequency, nPrevTime, nCurrTime, nElapsedCounter;
//获取频率,每秒钟的嘀嗒个数
QueryPerformanceFrequency(&nFrequency);
//获取第一个嘀嗒数
QueryPerformanceCounter(&nPrevTime);
//TODO: 做一些事情
Sleep(5)
//获取第二个嘀嗒数
QueryPerformanceCounter(&nCurrTime);
//中间间隔的嘀嗒数
nElapsedCounter.QuadPart = nCurrTime.QuadPart - nPrevTime.QuadPart;
//计算现实的消耗时间
nElapsedCounter.QuadPart * 1000.0f / nFrequency.QuadPart;
因为 QueryPerformanceFrequency 返回的是微妙,所以这里乘以 1000,换算成毫秒单位。SDL 中一样存在这样的 API 接口:
Uint64 SDL_GetPerformanceCounter(void)
Uint64 SDL_GetPerformanceFrequency(void)
用法和前面的 Windows 版本没什么区别,只不过名字和返回值和参数类型略有差异而已。
游戏中帧率的计算方法
知道了游戏中时间的计算方法,那么就可以学习如何计算游戏中的帧率,首先说一下什么是“帧”。
帧就是影像动画中最小单位的单幅影像画面,相当于电影胶片上的每一格镜头。 一帧就是一幅静止的画面,连续的帧就形成动画,如电视图象等。 我们通常说帧率,简单地说,就是在1秒钟时间里传输的图片的帧数,也可以理解为图形处理器每秒钟能够刷新几次,通常用 fps(Frames Per Second)表示。每一帧都是静止的图像,快速连续地显示帧便形成了运动的假象。高的帧率可以得到更流畅、更逼真的动画。
既然知道帧率的概念,结合上面讲述游戏中计算时间的方法,计算帧率是非常容易的。首先我们先通过 SDL_GetPerformanceCounter 得到一帧消耗的嘀嗒数,然后除以 SDL_GetPerformanceFrequency 获取的频率,这样就可以得到一帧所消耗的时间,这个时间单位是纳秒,所以我们可以通过 1 * 1000 * 1000 除以上面计算的时间就可以得到我们想要的帧率了。
Uint64 nFrequency, nPrevCounter, nCurrCounter, nElapsedCounter;
float elapsed = 0.0f, totalTime = 0.0f;
int fps = 0, fpsCount = 0;
nFrequency = SDL_GetPerformanceFrequency();
nPrevCounter = SDL_GetPerformanceCounter();
while (!quit) {
if (SDL_PollEvent(&evt)) {
if (evt.type == SDL_QUIT) {
quit = 1;
}
}
else {
nCurrCounter = SDL_GetPerformanceCounter();
nElapsedCounter = nCurrCounter - nPrevCounter;
nPrevCounter = nCurrCounter;
//前后两帧的耗时(ms)
elapsed = (nElapsedCounter * 1000.0f) / nFrequency;
//清除背景
SDL_SetRenderDrawColor(pRenderer, 0, 0, 0, 255);
SDL_RenderClear(pRenderer);
//TODO: 绘制其它
//显示图像
SDL_RenderPresent(pRenderer);
totalTime += elapsed;
//已经过了 1 秒
if (totalTime > 1000.0f) {
totalTime -= 1000.0f;
fps = fpsCount;
fpsCount = 1;
SDL_Log("%dn", fps);
}
else {
fpsCount++;
}
}
}
其中 fps 中保存的就是当前游戏的帧率。