1 / 37

SDL Time

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,

alvis
Download Presentation

SDL Time

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. SDLTime 靜宜大學資工系 蔡奇偉 副教授 2006-2007

  2. 內容大綱 • 前言 • SDL 的計時函式 • 傳統的遊戲迴圈 • 設計一個彈性的時鐘系統 • 產生平順的動畫 • 參考文件

  3. 前言

  4. SDL 的計時函式 • 在 SDL_Init() 中加入 SDL_INIT_TIMER • SDL_AddTimer • SDL_RemoveTimer • SDL_GetTicks • SDL_Delay

  5. SDL_AddTimer • SDL_TimerID SDL_AddTimer ( • Uint32 interval, • SDL_NewTimerCallback callback, • void *param • ); • 建立一個新的定時器,使得 interval毫秒之後,執行以 param為引數的回呼函式 callback。若回呼函式的傳回值等於 interval,則是週期性的定時器,否則是一次性的定時器。 • 函式的傳回值是此定時器的代碼。

  6. 3. 回呼函式的原型宣告如下: • typedef Uint32 (*SDL_NewTimerCallback)( • Uint32 interval, • void *param • ); • 在多執行緒的程式中,如果回呼函式不在主執行緒中執行,則不要呼叫除了 SDL_PushEvent() 以外的任何函式。 • 在大部分的平台上,定時器的精細度至少是 10 毫秒。因此對這些平台而言,你指定 12 毫秒或 20 毫秒的定時器間隔時間,兩者的效果其實是相同的。

  7. SDL_RemoveTimer SDL_bool SDL_RemoveTimer(SDL_TimerID id); 移除之前用 SDL_AddTimer() 所建立代碼為 id的定時器。

  8. 範例 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;

  9. 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); • }

  10. 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);

  11. 前述的定時器比較適合用於製作只含少數物件的動畫,並不適合用於有大量物件的遊戲動畫。前述的定時器比較適合用於製作只含少數物件的動畫,並不適合用於有大量物件的遊戲動畫。

  12. SDL_GetTicks Uint32 SDL_GetTicks (void); 取得 SDL_Init() 函式執行後到目前為止的間隔毫秒數。請注意:32 位元的正整數傳回值最多能容納約 49 天,若超出這個範圍,傳回值會繞回從 0 開始,而得到不正確的時間。對大部分的遊戲程式而言,這個限制並不太會造成影響,因為幾乎沒有玩家會連續玩超過 49 天。

  13. 以下的程式行執行之後,變數 duration 的值將是函式 TestFunc() 執行所花的秒數。 範例 Uint32 before = SDL_GetTicks(); TestFunc(); float duration = (SDL_GetTicks() – before ) / 1000.0;

  14. SDL_Delay void SDL_Delay (Uint32 ms); 此函式使得程式等待至少ms毫秒之後才結束執行。作業系統的程序排程 (scheduling) 可能會造成等待時間的增長。此外,對等待時間的指定,大部分平台必須以 10 毫秒為最小單位(稱為 delay granularity),換句話說,對這些電腦而言,你指定 12 毫秒或 20 毫秒的等待時間,兩者的效果其實是相同的。

  15. 為了避免畫面的閃爍,遊戲畫面的更新率應至少每秒 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); • }

  16. 範例 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); • }

  17. 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); • }

  18. 範例 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;

  19. 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); • }

  20. 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 • }

  21. 缺點 上述的動畫範例程式

  22. 畫格時間的差異

  23. 設計一個彈性的時鐘系統 • 可靠的時鐘與計時器。 • 時鐘必須克服遊戲迴圈執行時間上的差異性。 • 時鐘必須避免累積誤差。 • 允許使用多個獨立的計時器,以控制遊戲的不同的部分。 • 計時器具有暫停功能。 • 計時器能夠設定成不同的時間倍率。

  24. 時鐘的累積誤差

  25. 時鐘系統的 UML 圖

  26. TimeSource.h // 時間來源的介面定義 #ifndef TIMESOURCE_H_ #define TIMESOURCE_H_ class TimeSource { public: virtual ~TimeSource() {}; virtual double GetTime() const = 0; }; #endif

  27. TimeSourceSDL.h // 使用 Win32 的高精度效能計數器為時間來源 #ifndef TIMESOURCESDL_H_ #define TIMESOURCESDL_H_ #include "TimeSource.h" class TimeSourceSDL : public TimeSource { public: TimeSourceSDL () {}; virtual double GetTime() const; }; #endif

  28. TimeSourceSDL.cpp • #include <SDL/SDL.h> • #include "TimeSourceSDL.h" • double TimeSourceSDL::GetTime () const • { • return SDL_GetTicks()/1000.0; // 以秒為單位 • }

  29. Clock 物件的主要方法 • Clock(const TimeSource * pSource = NULL); • 建構函式。不等於 NULL 的參數 pSource用來指定所使用的時間來源。譬如底下的 gameClock 將採用 CPU 高精度效能計數器做為時間來源: • Clock gameClock(new TimeSourceHRWin32); • void SetTimeSource(const TimeSource * pSource); • 指定 pSource為時間來源。 • double GetTime(); • 取得目前的遊戲時間。

  30. double GetFrameDuration(); • 取得前後畫格的時間差。 • int GetFrameNumber(); • 取得目前畫格的序號(序號隨遊戲迴圈而逐次加 1)。 • float GetFrameRate(); • 取得遊戲目前的每秒播放畫格數。 • void SetFiltering (int frameWindow, double frameDefault); • 設定均化過濾器採樣的數目,以及預設的畫格時間長度。 • void FrameStep(); • 更新時鐘,然後通知採用此時鐘的計時器進行更新。

  31. Timer 物件的主要方法 • Timer(Clock & clock, float fScale = 1.0f); • 建構函式。採用參數 clock 為計時器的時鐘。譬如 • Timer myTimer(myClock); • 參數 fScale 用來設定計時器的時間快慢(見底下說明)。 • double GetTime(); • 取得計時器目前的時間。 • double GetFrameDuration(); • 取得前後畫格的時間差。

  32. void Pause (bool bOn); • 暫停計時器。 • void SetScale (float fScale); • 設定計時器的時間比例。譬如:若 fScale等於 2,則計時器比時鐘快 2 倍;若 fScale 等於 0.5,則計時器比時鐘慢 2 倍。計時器時間比例的預設值為 1.0,也就是說,和時鐘的時間相同。 • bool IsPaused (); • 檢查計時器是否處於暫停的狀態。 • float GetScale (); • 取得計時器的時間比例值。

  33. Clock 與 Timer 的使用方式 • 定義具有唯一性的 Clock 物件當作遊戲時鐘,譬如:Clock gameClock (new TimeSourceHRWin32); • 在遊戲迴圈的開頭(或結尾)呼叫gameClock.FrameStep();來更新遊戲時鐘與相關計時器。 • 定義一個計時器來記錄遊戲的世界時間。譬如:Timer gameTimer(gameClock); • 依據遊戲的需要定義其他的計時器,譬如:我們可以用一個計時器用於 GUI、另一個計時器來控制影片的播放、…、等等。 • 呼叫計時器的 GetFrameDuration () 所得的畫格時間差,可以用來計算遊戲角色的移動量或用來控制動畫的播放速率。

  34. 註: 為什麼要使用 gameTimer 而不直接用 gameClock 來記錄遊戲的世界時間呢?

  35. SDXNotes: • 在 CDX9App 類別中: • 我們定義底下兩個資料成員: • Clock*m_pGameClock; // 遊戲時鐘 • Timer*m_pGameTimer; // 遊戲主計時器 • 並在 CDX9App 建構函式中,設定好了它們的初值。 • 在 CDX9App::Run() 中,SDX 已經替你呼叫了 • m_pGameClock->FrameStep(); • 所以你寫的 GameRun() 函式不需要再呼叫一次。

  36. 產生平順的動畫

  37. 參考文件 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

More Related