1. 程式人生 > >SDL2系列教程9-定時:幀率,物理,動畫

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