定时
SDL为计时提供简单但方便的API。时序有许多应用,包括FPS计算和上限,分析程序的哪些部分花费最多时间,以及任何应该基于时间的模拟,例如物理和动画。
最基本的计时形式是SDL_GetTicks()。此函数只返回自SDL初始化以来经过的滴答数。一个刻度是一毫秒,是物理模拟和动画的可容忍分辨率。
Uint32 ticks = SDL_GetTicks()
刻度总是在增加 - SDL不提供间隔之间的时间,暂停全局计时器或类似的东西。但是,所有这些功能都可以相对简单地实现。例如,您可以创建一个管理单独和可暂停计时器的计时类。您真正需要的就是SDL为您提供的服务; 全球计时器。
例如,要以时间间隔计时,只需在开始和结束时请求时间......
Uint32 start = SDL_GetTicks(); // Do long operation Uint32 end = SDL_GetTicks(); float secondsElapsed = (end - start) / 1000.0f;
性能计数器
虽然SDL_GetTicks()在大多数情况下都足够好,但它可以达到的最小时间间隔是1毫秒。但是,如果你想要亚毫秒级的操作,或者想要比千分之一秒更精确的话,该怎么办?这就是SDL_GetPerformanceCounter()的用武之地。性能计数器是系统特定的高分辨率计时器,通常在微秒或纳秒的范围内。
由于性能计数器是特定于系统的,因此您实际上并不知道分辨率是多少。因此,函数SDL_GetPerformanceFrequency():它为您提供每秒性能计数器滴答数。
否则,该系统的使用方式与刻度完全相同。要更精确地计时间隔,请捕获起始和结束性能计数器值。
Uint64 start = SDL_GetPerformanceCounter(); // Do some operation Uint64 end = SDL_GetPerformanceCounter(); float secondsElapsed = (end - start) / (float)SDL_GetPerformanceFrequency();
帧率
定时的一个常见应用是计算程序运行时的FPS或每秒帧数。框架只是主游戏或程序循环的一次迭代。因此,计时非常简单:记录每帧开始和结束的时间。然后,以某种形式输出经过的时间或其倒数(FPS)。
bool running = true; while (running) { Uint64 start = SDL_GetPerformanceCounter(); // Do event loop // Do physics loop // Do rendering loop Uint64 end = SDL_GetPerformanceCounter(); float elapsed = (end - start) / (float)SDL_GetPerformanceFrequency(); cout << "Current FPS: " << to_string(1.0f / elapsed) << endl; }
加盖帧率
除了性能分析,您可能需要计算您的FPS以限制它。限制你的FPS是有用的,因为如果你试图每秒更新屏幕太多次,框架将开始相互重叠 - 这是屏幕撕裂。此外,限制您的FPS允许您的程序不使用给予它的所有CPU资源,从而释放用户的计算机来处理其他任务。虽然理想情况下,这些额外的资源可用于改善游戏玩法或图形。
限制你的FPS非常简单:只需从你想要的时间减去你的帧时间,然后等待与SDL_Delay()的差异。但是,此功能只需要几毫秒的延迟 - 遗憾的是,您无法以非常高的精度限制FPS。(至少在SDL上 - 查看std :: chrono了解更多信息。)
您通常希望将FPS限制为60,因为这是迄今为止最常见的刷新率。这意味着每帧花费16和2/3毫秒。请注意,您可以轻松更改此上限。
bool running = true; while (running) { Uint64 start = SDL_GetPerformanceCounter(); // Do event loop // Do physics loop // Do rendering loop Uint64 end = SDL_GetPerformanceCounter(); float elapsedMS = (end - start) / (float)SDL_GetPerformanceFrequency() * 1000.0f; // Cap to 60 FPS SDL_Delay(floor(16.666f - elapsedMS)); }
垂直同步
另一种防止屏幕撕裂的方法是使用VSync或垂直同步。这种技术只是在显示窗口之前调用SDL_RenderPresent()等待正确的时间间隔。基本上,它会为你限制你的FPS。
要使用它,必须在创建渲染器时启用VSync。为此,只需将标志SDL_RENDERER_PRESENTVSYNC传递给SDL_CreateRenderer()即可。对SDL_RenderPresent()的后续调用将在显示窗口之前等待。
SDL_Renderer* renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_PRESENTVSYNC | SDL_RENDERER_ACCELERATED );
物理
正如我所提到的,你需要物理模拟的时间。到目前为止,你所能做的一切都是基于框架的。例如,您的播放器每帧可移动20个像素。但是,这不是一个好办法。如果您的帧速率发生变化,如果它的变化,您的物理“时间”也会变慢或变快。解决方案只是基于时间进行模拟 - 这样,无论何时更新物理,它都会自动使用正确的时间间隔。
这样做非常简单:存储物理上次更新的时间(按实体或全局)并计算需要更新以获得当前时间。这是您的增量时间(dT)值 - 将其乘以基于时间的计算(例如位置+ =速度* dT)。
bool running; Uint32 lastupdate = SDL_GetTicks(); while (running) { // Event loop // Physics loop Uint32 current = SDL_GetTicks(); // Calculate dT (in seconds) float dT = (current - lastUpdate) / 1000.0f; for ( /* objects */ ) { object.position += object.velocity * dT; } // Set updated time lastUpdate = current; // Rendering loop }
动画
注意:我们将在课堂上更详细地介绍这一点。
与物理一样,正确的动画也依赖于时间。虽然让你的精灵动画FPS依赖于你的全局FPS并不是那么糟糕,但是这会使动画看起来很不好,迫使你限制你的FPS,并且需要为每个动画对象提供相同的FPS。
解决方案与物理学几乎完全相同; 计算dT值并使用它来决定绘制哪个帧。但是,在这里,您必须跟踪每个动画对象的上次更新时间,并且必须仅在有足够的时间来翻转帧时进行更新。下载一个例子。
float animatedFPS = 24.0f; bool running; while (running) { // Event loop // Physics loop // Rendering loop Uint32 current = SDL_GetTicks(); // Calculate dT (in seconds) for ( /* objects */ ) { float dT = (current - object.lastUpdate) / 1000.0f; int framesToUpdate = floor(dT / (1.0f / animatedFPS)); if (framesToUpdate > 0) { object.lastFrame += framesToUpdate; object.lastFrame %= object.numFrames; object.lastUpdate = current; } render(object.frames[object.lastFrame]); } }