380 likes | 619 Views
SDL Time. 靜宜大學資工系 蔡奇偉 副教授 2006-2007. 內容大綱. 前言 SDL 的計時函式 傳統的遊戲迴圈 設計一個彈性的時鐘系統 產生平順的動畫 參考文件. 前言. SDL 的計時函式. 在 SDL_Init() 中加入 SDL_INIT_TIMER SDL_AddTimer SDL_RemoveTimer SDL_GetTicks SDL_Delay. SDL_AddTimer. SDL_TimerID SDL_AddTimer ( Uint32 interval,
E N D
SDLTime 靜宜大學資工系 蔡奇偉 副教授 2006-2007
內容大綱 • 前言 • SDL 的計時函式 • 傳統的遊戲迴圈 • 設計一個彈性的時鐘系統 • 產生平順的動畫 • 參考文件
SDL 的計時函式 • 在 SDL_Init() 中加入 SDL_INIT_TIMER • SDL_AddTimer • SDL_RemoveTimer • SDL_GetTicks • SDL_Delay
SDL_AddTimer • SDL_TimerID SDL_AddTimer ( • Uint32 interval, • SDL_NewTimerCallback callback, • void *param • ); • 建立一個新的定時器,使得 interval毫秒之後,執行以 param為引數的回呼函式 callback。若回呼函式的傳回值等於 interval,則是週期性的定時器,否則是一次性的定時器。 • 函式的傳回值是此定時器的代碼。
3. 回呼函式的原型宣告如下: • typedef Uint32 (*SDL_NewTimerCallback)( • Uint32 interval, • void *param • ); • 在多執行緒的程式中,如果回呼函式不在主執行緒中執行,則不要呼叫除了 SDL_PushEvent() 以外的任何函式。 • 在大部分的平台上,定時器的精細度至少是 10 毫秒。因此對這些平台而言,你指定 12 毫秒或 20 毫秒的定時器間隔時間,兩者的效果其實是相同的。
SDL_RemoveTimer SDL_bool SDL_RemoveTimer(SDL_TimerID id); 移除之前用 SDL_AddTimer() 所建立代碼為 id的定時器。
範例 SDL12: 利用定時器來製作一個球繞螢幕中心旋轉的動畫。 • struct BallObj { • Uint16 x, y; // ball position • SDL_Surface *image; // ball image • }; • BallObj redball = {0, 0, NULL}; • BallObj blueball = {(SCREEN_WIDTH-BALL_WIDTH)/2, • (SCREEN_HEIGHT - BALL_HEIGHT)/2, NULL}; • Uint32 background_color;
void show_surface( int x, int y, SDL_Surface* source) • { • SDL_Rect offset = {x, y, 0, 0}; • SDL_BlitSurface(source, NULL, screen, &offset); • } • inline void show_ball (BallObj *ball) • { • show_surface(ball->x, ball->y, ball->image); • }
const float PERIOD = 60000.0; // in millisecond const float RADIUS = 200; // in pixels Uint32 TimerCallback (Uint32 interval, void *param) { static int n = 0; BallObj *ball = (BallObj *) param; double angle_inc = 2.0*M_PI*interval /PERIOD; // angular increment int x = RADIUS * cos(n*angle_inc) - BALL_WIDTH/2 + SCREEN_WIDTH/2; int y = RADIUS * sin(n*angle_inc) - BALL_HEIGHT/2 + SCREEN_HEIGHT/2; SDL_Rect rects[2] = {{ball->x, ball->y, BALL_WIDTH, BALL_HEIGHT}, {x, y, BALL_WIDTH, BALL_HEIGHT}}; SDL_FillRect(screen, &rects[0], background_color); // Update ball position and then show it. ball->x = x; ball->y = y; show_ball(ball); SDL_UpdateRects(screen, 2, rects); n++; return interval; } 在 main () 函式中: SDL_TimerID Timer1 = SDL_AddTimer(1000, TimerCallback, &redball);
前述的定時器比較適合用於製作只含少數物件的動畫,並不適合用於有大量物件的遊戲動畫。前述的定時器比較適合用於製作只含少數物件的動畫,並不適合用於有大量物件的遊戲動畫。
SDL_GetTicks Uint32 SDL_GetTicks (void); 取得 SDL_Init() 函式執行後到目前為止的間隔毫秒數。請注意:32 位元的正整數傳回值最多能容納約 49 天,若超出這個範圍,傳回值會繞回從 0 開始,而得到不正確的時間。對大部分的遊戲程式而言,這個限制並不太會造成影響,因為幾乎沒有玩家會連續玩超過 49 天。
以下的程式行執行之後,變數 duration 的值將是函式 TestFunc() 執行所花的秒數。 範例 Uint32 before = SDL_GetTicks(); TestFunc(); float duration = (SDL_GetTicks() – before ) / 1000.0;
SDL_Delay void SDL_Delay (Uint32 ms); 此函式使得程式等待至少ms毫秒之後才結束執行。作業系統的程序排程 (scheduling) 可能會造成等待時間的增長。此外,對等待時間的指定,大部分平台必須以 10 毫秒為最小單位(稱為 delay granularity),換句話說,對這些電腦而言,你指定 12 毫秒或 20 毫秒的等待時間,兩者的效果其實是相同的。
為了避免畫面的閃爍,遊戲畫面的更新率應至少每秒 30 張的畫格,即畫格平均時間需少於 33 毫秒。 遊戲迴圈 • while (!bQuit) • { • Uint32 timeStart = SDL_GetTicks(); • while (SDL_PollEvent(&event)) • { • // 處理事件 • } • // 更新遊戲狀態 • // 更新遊戲畫面 • // 取得畫格時間 • Uint32 frameDuration = SDL_GetTicks() – timeStart; • // 如果畫格時間過短,則呼叫 SDL_Delay() 來補足 • if (frameDuration < 33) • SDL_Delay(33- frameDuration); • }
範例 SDL13: 用前述的遊戲迴圈來製作一個球繞螢幕中心旋轉的動畫。 • int nFrame = 0; • SDL_Event event; /* Event structure */ • while (!bQuit) • { • Uint32 timeStart = SDL_GetTicks(); • while (SDL_PollEvent(&event)) • { • // 處理事件 • } • update_redball(nFrame); • nFrame++; • // 取得畫格時間,如果畫格時間過短,則呼叫 SDL_Delay() 來補足 • Uint32 frameDuration = SDL_GetTicks() - timeStart ; • if (frameDuration < desired_frame_duration) • SDL_Delay(desired_frame_duration- frameDuration); • }
int desired_frame_duration = 33; • const float PERIOD = 6000.0; // period in millisecond • const float RADIUS = 200; // in pixels • double angle_inc = 2.0*M_PI*desired_frame_duration/PERIOD; // angular increment • void update_redball (int nFrame) • { • int x = RADIUS * cos(nFrame*angle_inc) - BALL_WIDTH/2 + SCREEN_WIDTH/2; • int y = RADIUS * sin(nFrame*angle_inc) - BALL_HEIGHT/2 + SCREEN_HEIGHT/2; • SDL_Rect rects[2] = {{redball.x, redball.y, BALL_WIDTH, BALL_HEIGHT}, • {x, y, BALL_WIDTH, BALL_HEIGHT}}; • SDL_FillRect(screen, &rects[0], background_color); • // Update redball position and then show it. • redball.x = x; • redball.y = y; • show_ball(&redball); • SDL_UpdateRects(screen, 2, rects); • }
範例 SDL14: 製作紅球繞螢幕中心旋轉、藍球水平移動的動畫。 • struct BallObj { • Uint16 x, y; // ball position • int dx, dy; // ball velocity • SDL_Surface *image; // ball image • }; • BallObj redball = {0, 0, 0, 0, NULL}; • BallObj blueball = {(SCREEN_WIDTH-BALL_WIDTH)/2, • (SCREEN_HEIGHT - BALL_HEIGHT)/2, • 5, 0, NULL}; • int desired_frame_duration = 33;
while (!bQuit) • { • Uint32 timeStart = SDL_GetTicks(); • while (SDL_PollEvent(&event)) • { • // 處理事件 • } • SDL_FillRect(screen,NULL, background_color); // 清除螢幕 • draw_blueball(); • draw_redball(nFrame); • SDL_Flip(screen); • nFrame++; • // 取得畫格時間,如果畫格時間過短,則呼叫 SDL_Delay() 來補足 • Uint32 frameDuration = SDL_GetTicks() - timeStart; • if (frameDuration < desired_frame_duration) • SDL_Delay(desired_frame_duration- frameDuration); • }
void draw_redball (int nFrame) • { • int x = RADIUS * cos(nFrame*angle_inc) - BALL_WIDTH/2 + SCREEN_WIDTH/2; • int y = RADIUS * sin(nFrame*angle_inc) - BALL_HEIGHT/2 + SCREEN_HEIGHT/2; • // Update ball position and then show it. • redball.x = x; • redball.y = y; • show_ball(&redball); • } • void draw_blueball () • { • int x = blueball.x + blueball.dx; • restrict(x, 0, SCREEN_WIDTH - BALL_WIDTH); • if (x != blueball.x) • { • blueball.x = x; • show_ball(&blueball); • } • else • blueball.dx = -blueball.dx; // reverse the movement • }
缺點 上述的動畫範例程式
設計一個彈性的時鐘系統 • 可靠的時鐘與計時器。 • 時鐘必須克服遊戲迴圈執行時間上的差異性。 • 時鐘必須避免累積誤差。 • 允許使用多個獨立的計時器,以控制遊戲的不同的部分。 • 計時器具有暫停功能。 • 計時器能夠設定成不同的時間倍率。
TimeSource.h // 時間來源的介面定義 #ifndef TIMESOURCE_H_ #define TIMESOURCE_H_ class TimeSource { public: virtual ~TimeSource() {}; virtual double GetTime() const = 0; }; #endif
TimeSourceSDL.h // 使用 Win32 的高精度效能計數器為時間來源 #ifndef TIMESOURCESDL_H_ #define TIMESOURCESDL_H_ #include "TimeSource.h" class TimeSourceSDL : public TimeSource { public: TimeSourceSDL () {}; virtual double GetTime() const; }; #endif
TimeSourceSDL.cpp • #include <SDL/SDL.h> • #include "TimeSourceSDL.h" • double TimeSourceSDL::GetTime () const • { • return SDL_GetTicks()/1000.0; // 以秒為單位 • }
Clock 物件的主要方法 • Clock(const TimeSource * pSource = NULL); • 建構函式。不等於 NULL 的參數 pSource用來指定所使用的時間來源。譬如底下的 gameClock 將採用 CPU 高精度效能計數器做為時間來源: • Clock gameClock(new TimeSourceHRWin32); • void SetTimeSource(const TimeSource * pSource); • 指定 pSource為時間來源。 • double GetTime(); • 取得目前的遊戲時間。
double GetFrameDuration(); • 取得前後畫格的時間差。 • int GetFrameNumber(); • 取得目前畫格的序號(序號隨遊戲迴圈而逐次加 1)。 • float GetFrameRate(); • 取得遊戲目前的每秒播放畫格數。 • void SetFiltering (int frameWindow, double frameDefault); • 設定均化過濾器採樣的數目,以及預設的畫格時間長度。 • void FrameStep(); • 更新時鐘,然後通知採用此時鐘的計時器進行更新。
Timer 物件的主要方法 • Timer(Clock & clock, float fScale = 1.0f); • 建構函式。採用參數 clock 為計時器的時鐘。譬如 • Timer myTimer(myClock); • 參數 fScale 用來設定計時器的時間快慢(見底下說明)。 • double GetTime(); • 取得計時器目前的時間。 • double GetFrameDuration(); • 取得前後畫格的時間差。
void Pause (bool bOn); • 暫停計時器。 • void SetScale (float fScale); • 設定計時器的時間比例。譬如:若 fScale等於 2,則計時器比時鐘快 2 倍;若 fScale 等於 0.5,則計時器比時鐘慢 2 倍。計時器時間比例的預設值為 1.0,也就是說,和時鐘的時間相同。 • bool IsPaused (); • 檢查計時器是否處於暫停的狀態。 • float GetScale (); • 取得計時器的時間比例值。
Clock 與 Timer 的使用方式 • 定義具有唯一性的 Clock 物件當作遊戲時鐘,譬如:Clock gameClock (new TimeSourceHRWin32); • 在遊戲迴圈的開頭(或結尾)呼叫gameClock.FrameStep();來更新遊戲時鐘與相關計時器。 • 定義一個計時器來記錄遊戲的世界時間。譬如:Timer gameTimer(gameClock); • 依據遊戲的需要定義其他的計時器,譬如:我們可以用一個計時器用於 GUI、另一個計時器來控制影片的播放、…、等等。 • 呼叫計時器的 GetFrameDuration () 所得的畫格時間差,可以用來計算遊戲角色的移動量或用來控制動畫的播放速率。
註: 為什麼要使用 gameTimer 而不直接用 gameClock 來記錄遊戲的世界時間呢?
SDXNotes: • 在 CDX9App 類別中: • 我們定義底下兩個資料成員: • Clock*m_pGameClock; // 遊戲時鐘 • Timer*m_pGameTimer; // 遊戲主計時器 • 並在 CDX9App 建構函式中,設定好了它們的初值。 • 在 CDX9App::Run() 中,SDX 已經替你呼叫了 • m_pGameClock->FrameStep(); • 所以你寫的 GameRun() 函式不需要再呼叫一次。
參考文件 Noel Llopis. The Clock: Keeping Your Finger on the Pulse of the Game. Game Programming Gems IV pp 27-34. 2004. Dan Ricart. Achieving Frame Rate Independent Game Movement. http://www.gamedev.net/reference/articles/article1604.asp