SDL Time

37
SDL Time 靜靜靜靜靜靜靜 靜靜靜 靜靜靜 2006-2007

description

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, - PowerPoint PPT Presentation

Transcript of SDL Time

Page 1: SDL Time

SDLTime

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

Page 2: SDL Time

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

Page 3: SDL Time

前言

Page 4: SDL Time

SDL 的計時函式 在 SDL_Init() 中加入 SDL_INIT_TIMER

SDL_AddTimer

SDL_RemoveTimer

SDL_GetTicks

SDL_Delay

Page 5: SDL Time

SDL_AddTimer

SDL_TimerID SDL_AddTimer (

Uint32 interval,

SDL_NewTimerCallback callback,

void *param

);

1. 建立一個新的定時器,使得 interval 毫秒之後,執行以 param 為引數的回呼函式 callback 。若回呼函式的傳回值等於 interval ,則是週期性的定時器,否則是一次性的定時器。

2. 函式的傳回值是此定時器的代碼。

Page 6: SDL Time

typedef Uint32 (*SDL_NewTimerCallback)(

Uint32 interval,

void *param

);

3. 回呼函式的原型宣告如下:

4. 在多執行緒的程式中,如果回呼函式不在主執行緒中執行,則不要呼叫除了 SDL_PushEvent() 以外的任何函式。

5. 在大部分的平台上,定時器的精細度至少是 10 毫秒。因此對這些平台而言,你指定 12 毫秒或 20

毫秒的定時器間隔時間,兩者的效果其實是相同的。

Page 7: SDL Time

SDL_RemoveTimer

SDL_bool SDL_RemoveTimer(SDL_TimerID id);

移除之前用 SDL_AddTimer() 所建立代碼為 id 的定時器。

Page 8: SDL Time

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

Page 9: SDL Time

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

}

Page 10: SDL Time

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

Page 11: SDL Time

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

Page 12: SDL Time

SDL_GetTicks

Uint32 SDL_GetTicks (void);

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

Page 13: SDL Time

範例 以下的程式行執行之後,變數 duration 的值將是函式 TestFunc() 執行所花的秒數。

Uint32 before = SDL_GetTicks();

TestFunc();

float duration = (SDL_GetTicks() – before ) / 1000.0;

Page 14: SDL Time

SDL_Delay

void SDL_Delay (Uint32 ms);

此函式使得程式等待至少 ms 毫秒之後才結束執行。作業系統的程序排程 (scheduling) 可能會造成等待時間的增長。此外,對等待時間的指定,大部分平台必須以 10 毫秒為最小單位(稱為 delay granularit

y ),換句話說,對這些電腦而言,你指定 12 毫秒或 20 毫秒的等待時間,兩者的效果其實是相同的。

Page 15: SDL Time

遊戲迴圈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 毫秒。

Page 16: SDL Time

範例 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);

}

Page 17: SDL Time

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

}

Page 18: SDL Time

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

Page 19: SDL Time

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

Page 20: SDL Time

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}

Page 21: SDL Time

缺點

上述的動畫範例程式

Page 22: SDL Time

畫格時間的差異

Page 23: SDL Time

設計一個彈性的時鐘系統可靠的時鐘與計時器。時鐘必須克服遊戲迴圈執行時間上的差

異性。時鐘必須避免累積誤差。允許使用多個獨立的計時器,以控制遊

戲的不同的部分。計時器具有暫停功能。計時器能夠設定成不同的時間倍率。

Page 24: SDL Time

時鐘的累積誤差

Page 25: SDL Time

時鐘系統的 UML 圖

Page 26: SDL Time

TimeSource.h// 時間來源的介面定義

#ifndef TIMESOURCE_H_#define TIMESOURCE_H_

class TimeSource{public: virtual ~TimeSource() {}; virtual double GetTime() const = 0;};

#endif

Page 27: SDL Time

TimeSourceSDL.h// 使用 Win32 的高精度效能計數器為時間來源

#ifndef TIMESOURCESDL_H_#define TIMESOURCESDL_H_

#include "TimeSource.h"

class TimeSourceSDL : public TimeSource{public: TimeSourceSDL () {}; virtual double GetTime() const;};

#endif

Page 28: SDL Time

TimeSourceSDL.cpp#include <SDL/SDL.h>#include "TimeSourceSDL.h"

double TimeSourceSDL::GetTime () const{

return SDL_GetTicks()/1000.0; // 以秒為單位}

Page 29: SDL Time

Clock 物件的主要方法Clock (const TimeSource * pSource = NULL);

建構函式。不等於 NULL 的參數 pSource 用來指定所使用的時間來源。譬如底下的 gameClock 將採用 CPU 高精度效能計數器做為時間來源:

Clock gameClock(new TimeSourceHRWin32);

void SetTimeSource (const TimeSource * pSource);

指定 pSource 為時間來源。

double GetTime ();

取得目前的遊戲時間。

Page 30: SDL Time

double GetFrameDuration ();

取得前後畫格的時間差。

int GetFrameNumber ();

取得目前畫格的序號(序號隨遊戲迴圈而逐次加 1 )。

float GetFrameRate();

取得遊戲目前的每秒播放畫格數。

void SetFiltering (int frameWindow, double frameDefault);

設定均化過濾器採樣的數目,以及預設的畫格時間長度。

void FrameStep();

更新時鐘,然後通知採用此時鐘的計時器進行更新。

Page 31: SDL Time

Timer 物件的主要方法Timer (Clock & clock, float fScale = 1.0f);

建構函式。採用參數 clock 為計時器的時鐘。譬如

Timer myTimer(myClock);

參數 fScale 用來設定計時器的時間快慢(見底下說明)。

double GetTime ();

取得計時器目前的時間。

double GetFrameDuration ();

取得前後畫格的時間差。

Page 32: SDL Time

void Pause (bool bOn);

暫停計時器。

void SetScale (float fScale);

設定計時器的時間比例。譬如:若 fScale 等於 2 ,則計時器比時鐘快 2 倍;若 fScale 等於 0.5 ,則計時器比時鐘慢 2 倍。計時器時間比例的預設值為 1.0 ,也就是說,和時鐘的時間相同。

bool IsPaused ();

檢查計時器是否處於暫停的狀態。

float GetScale ();

取得計時器的時間比例值。

Page 33: SDL Time

Clock 與 Timer 的使用方式 定義具有唯一性的 Clock 物件當作遊戲時鐘,譬如:

Clock gameClock (new TimeSourceHRWin32);

在遊戲迴圈的開頭(或結尾)呼叫 gameClock.FrameStep();

來更新遊戲時鐘與相關計時器。 定義一個計時器來記錄遊戲的世界時間。譬如:

Timer gameTimer(gameClock);

依據遊戲的需要定義其他的計時器,譬如:我們可以用一個計時器用於 GUI 、另一個計時器來控制影片的播放、…、等等。

呼叫計時器的 GetFrameDuration () 所得的畫格時間差,可以用來計算遊戲角色的移動量或用來控制動畫的播放速率。

Page 34: SDL Time

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

註:

Page 35: SDL Time

SDX Notes:

在 CDX9App 類別中:

我們定義底下兩個資料成員:

Clock *m_pGameClock; // 遊戲時鐘

Timer *m_pGameTimer; // 遊戲主計時器

並在 CDX9App 建構函式中,設定好了它們的初值。

在 CDX9App::Run() 中, SDX 已經替你呼叫了

m_pGameClock->FrameStep();

所以你寫的 GameRun() 函式不需要再呼叫一次。

Page 36: SDL Time

產生平順的動畫

Page 37: SDL Time

參考文件

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