SDL Time
description
Transcript of SDL Time
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
);
1. 建立一個新的定時器,使得 interval 毫秒之後,執行以 param 為引數的回呼函式 callback 。若回呼函式的傳回值等於 interval ,則是週期性的定時器,否則是一次性的定時器。
2. 函式的傳回值是此定時器的代碼。
typedef Uint32 (*SDL_NewTimerCallback)(
Uint32 interval,
void *param
);
3. 回呼函式的原型宣告如下:
4. 在多執行緒的程式中,如果回呼函式不在主執行緒中執行,則不要呼叫除了 SDL_PushEvent() 以外的任何函式。
5. 在大部分的平台上,定時器的精細度至少是 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 millisecondconst 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 granularit
y ),換句話說,對這些電腦而言,你指定 12 毫秒或 20 毫秒的等待時間,兩者的效果其實是相同的。
遊戲迴圈while (!bQuit){
Uint32 timeStart = SDL_GetTicks();while (SDL_PollEvent(&event)){
// 處理事件}
// 更新遊戲狀態// 更新遊戲畫面
// 取得畫格時間Uint32 frameDuration = SDL_GetTicks() – timeStart;
// 如果畫格時間過短,則呼叫 SDL_Delay() 來補足if (frameDuration < 33)
SDL_Delay(33- frameDuration);}
為了避免畫面的閃爍,遊戲畫面的更新率應至少每秒 30 張的畫格,即畫格平均時間需少於 33 毫秒。
範例 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}
缺點
上述的動畫範例程式
畫格時間的差異
設計一個彈性的時鐘系統可靠的時鐘與計時器。時鐘必須克服遊戲迴圈執行時間上的差
異性。時鐘必須避免累積誤差。允許使用多個獨立的計時器,以控制遊
戲的不同的部分。計時器具有暫停功能。計時器能夠設定成不同的時間倍率。
時鐘的累積誤差
時鐘系統的 UML 圖
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 來記錄遊戲的世界時間呢?
註:
SDX Notes:
在 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