SDL2系列教程9-定时:帧率,物理,动画

定时

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]);
	}
}

猜你喜欢

转载自blog.csdn.net/cyf15238622067/article/details/82908866