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()的用武之地。效能計數器是系統特定的高解析度計時器,通常在微秒或納秒的範圍內。
由於效能計數器是特定於系統的,因此您實際上並不知道解析度是多少。因此,函式
否則,該系統的使用方式與刻度完全相同。要更精確地計時間隔,請捕獲起始和結束效能計數器值。
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]); } }