Presentation is loading. Please wait.

Presentation is loading. Please wait.

SDL – 2 事件處理 靜宜大學資訊工程系 蔡奇偉副教授 © 2007.

Similar presentations


Presentation on theme: "SDL – 2 事件處理 靜宜大學資訊工程系 蔡奇偉副教授 © 2007."— Presentation transcript:

1 SDL – 2 事件處理 靜宜大學資訊工程系 蔡奇偉副教授 © 2007

2 綱要 事件 事件迴圈 鍵盤事件的處理 滑鼠事件的處理 搖桿事件的處理 使用者自定事件的處理

3 視窗程式的事件驅動模型 視窗程式是採用事件驅動的模型,換句話說,程式的執行流程是根據事件發生的時間與種類而定。事件可因為使用者的操作而產生,如滑鼠移動、按下滑鼠鍵、按下鍵盤、選擇功能表的項目、隱藏的視窗被提到幕前、…、等,也可由硬體産生,如主機板上的時脈產生器(clock),或由軟體(作業系統或程式本身)產生。作業系統中,有一個稱之為訊息佇列(message queue)的資料結構,用來儲存事件的訊息。作業系統會負責把這些訊息送到適當的視窗應用程式來處理,如下一頁的圖片所示。

4 msg 1 msg 2 msg n SDL_Event e; bool bQuit = false;
// enter the event loop while (!bQuit) { while (SDL_PollEvent(&e)) switch(e.type) case SDL_QUIT: bQuit = true; break; case SDL_KEYDOWN: // Handle a KEYDOWN event . default: // Report an unhandled event } msg 1 msg 2 msg n

5 SDL_Event 結構 typedef union{ Uint8 type; SDL_ActiveEvent active; SDL_KeyboardEvent key; SDL_MouseMotionEvent motion; SDL_MouseButtonEvent button; SDL_JoyAxisEvent jaxis; SDL_JoyBallEvent jball; SDL_JoyHatEvent jhat; SDL_JoyButtonEvent jbutton; SDL_ResizeEvent resize; SDL_ExposeEvent expose; SDL_QuitEvent quit; SDL_UserEvent user; SDL_SywWMEvent syswm; } SDL_Event; 左方的 SDL_Event 是一個聯集合宣告,使其能夠含括各種事件的資訊。我們可以用第一欄 type 的值來分辦事件的類別,然後再依據類別來取出事件的詳細資料。

6 SDL 的事件類別 事件類別名稱 事件結構 說明 SDL_ACTIVEEVENT SDL_ActiveEvent 視窗啟動事件
SDL_KEYDOWN/UP SDL_KeyboardEvent 鍵盤事件 SDL_MOUSEMOTION SDL_MouseMotionEvent 滑鼠移動事件 SDL_MOUSEBUTTONDOWN/UP SDL_MouseButtonEvent 滑鼠按鍵事件 SDL_JOYAXISMOTION SDL_JoyAxisEvent 搖桿軸事件 SDL_JOYBALLMOTION SDL_JoyBallEvent 搖桿軌跡球事件 SDL_JOYHATMOTION SDL_JoyHatEvent 搖桿 Hat 鍵事件 SDL_JOYBUTTONDOWN/UP SDL_JoyButtonEvent 搖桿按鈕事件 SDL_QUIT SDL_QuitEvent 結束事件 SDL_SYSWMEVENT SDL_SysWMEvent 系統視窗事件 SDL_VIDEORESIZE SDL_ResizeEvent 視窗改變大小事件 SDL_VIDEOEXPOSE SDL_ExposeEvent 視窗暴露事件 SDL_USEREVENT SDL_UserEvent 使用者自定事件

7 SDL_PollEvent int SDL_PollEvent(SDL_Event *event);

8 範例 SDL_Event event; /* Event structure */ . /* Check for events */
while (SDL_PollEvent(&event)) { // Loop until there are no events left on the queue switch (event.type) { // Process the appropiate event type case SDL_KEYDOWN: // Handle a KEYDOWN event break; case SDL_MOUSEMOTION: default: // Report an unhandled event }

9 鍵盤處理 SDL_KEYDOWN 或 SDL_KEYUP typedef union{ Uint8 type;
SDL_KeyboardEvent key; } SDL_Event; typedef struct{ Uint8 scancode; SDLKey sym; SDLMod mod; Uint16 unicode; } SDL_keysym; typedef struct{ Uint8 type; Uint8 state; SDL_keysym keysym; } SDL_KeyboardEvent;

10 範例 while (SDL_PollEvent(&event)) { switch(event.type)
case SDL_KEYDOWN: if(event.key.keysym.sym == SDLK_LEFT) move_left(); break; }

11 SDL_KeyboardEvent typedef struct{ Uint8 type; // SDL_KEYDOWN 或 SDL_KEYUP Uint8 state; // SDL_PRESSED 或 SDL_RELEASED SDL_keysym keysym; // 包含按鍵的資訊 } SDL_KeyboardEvent; 當壓下或放開 鍵盤上的任一按鍵時,就會產生鍵盤事件。上面的結構用來記錄鍵盤事件的相關資訊。type 與 state 這兩欄其實記錄相同的資訊: 當按下鍵時,type = SDL_KEYDOWN 和 state = SDL_PRESSED。 放開鍵時, type = SDL_KEYUP 和 state = SDL_RELEASED。

12 SDL_keysym 按鍵資訊包含下列項目:
typedef struct{ Uint8 scancode; // 鍵盤硬體產生的描掃碼 SDLKey sym; // SDL 所定義的虛擬碼 SDLMod mod; // 修飾鍵 Uint16 unicode; // 按鍵的 Unicode 碼 } SDL_keysym; 對大部分的程式而言,只要檢查 sym 的值就夠了,稍微複雜的程式,頂多再檢查修飾鍵(如 Ctrl 和 Shift)是否有同時按住。

13 SDL 虛擬鍵 SDL 把鍵盤上的每一個按鍵都定義一個虛擬碼。下表列出其中常用者:(其他請參閱手冊 SDLKey 的說明) 虛擬碼 按鍵
SDLK_0 … SDLK_9 0 … 9 SDLK_a … SDLK_z a … z SDLK_UP, SDLK_DOWN, SDLK_LEFT, SDLK_RIGHT 上下左右鍵 SDLK_F1 … SDLK_F15 F1 … F15 SDLK_LSHIFT, SDLK_RSHIFT 左 SHIFT 鍵、右 SHIFT 鍵 SDLK_LALT, SDLK_RALT 左 ALT 鍵、右 ALT 鍵 SDLK_LCTRL, SDLK_RCTRL 左 CTRL 鍵、右 CTRL 鍵

14 修飾鍵 (Modifier) SDL 修飾鍵 意義 KMOD_NONE 無修飾鍵 KMOD_NUM 數字鎖住鍵開啟 KMOD_CAPS
大寫鍵開啟 KMOD_LCTRL, KMOD_RCTRL 按下左 CTRL 鍵、右 CTRL 鍵 KMOD_CTRL 按下任一 CTRL 鍵 KMOD_LSHIFT, KMOD_RSHIFT 按下左 SHIFT 鍵、右 SHIFT 鍵 KMOD_SHIFT 按下任一 SHIFT 鍵 KMOD_LALT, KMOD_RALT 按下左 ALT 鍵、右 ALT 鍵 KMOD_ALT 按下任一 ALT 鍵

15 範例 區別大小寫字母按鍵 char ch; while (SDL_PollEvent(&event)) {
switch(event.type) case SDL_KEYDOWN: SDLKey sym = event.key.keysym.sym; if(sym >= SDL_a && sym <= SDL_z) // 字母鍵 SDLMod mod = event.key.keysym.mod; if (mod & KMOD_SHIFT || mod & KMOD_CAPS) ch = sym - 32; // 大寫字母 else ch = sym; // 小寫字母 } break;

16 啟動連續鍵 int SDL_EnableKeyRepeat( int delay, // 按鍵多久後才啟動連續鍵(毫秒)
int interval // 連續鍵的間隔時間(毫秒) ); 我們可以用以下的預設值代入: #define SDL_DEFAULT_REPEAT_DELAY 500 #define SDL_DEFAULT_REPEAT_INTERVAL 30 不過,對遊戲程式而言,delay 最好設小一點,如 10,interval 保持 30 即可。

17 範例 SDL06 這個範例程式讓使用者用鍵盤在一張背景圖上移動一個球。按鍵的設定如下: 左移 右移 上移 下移 = 增加速度 - 減低速度 HOME 回歸中央

18 問題:如何使球的移動限制在螢幕範圍中?

19 const int SCREEN_WIDTH = 640;
const int SCREEN_HEIGHT = 480; const int SCREEN_BPP = 32; const char *WINDOW_TITLE = "Keyborad Event"; const int BALL_WIDTH = 64; const int BALL_HEIGHT = 64; bool bQuit = false; SDL_Surface *screen = NULL; SDL_Surface *background = NULL; SDL_Surface *ball = NULL; int ball_x = (SCREEN_WIDTH - BALL_WIDTH) /2; int ball_y = (SCREEN_HEIGHT - BALL_HEIGHT) /2; int ball_xvel = 0; int ball_yvel = 0; int ball_speed = 1; int ball_speed_inc = 0;

20 // 拷貝繪圖頁 source 至螢幕位置 (x, y)
void show_surface( int x, int y, SDL_Surface* source) { SDL_Rect offset = {x, y, 0, 0}; SDL_BlitSurface(source, NULL, screen, &offset); } // 更新螢幕 void update_screen () show_surface(0, 0, background); show_surface(ball_x, ball_y, ball);

21 void handle_keyDown (SDLKey sym)
{ switch (sym) case SDLK_UP: // 上移 ball_yvel = -ball_speed; break; case SDLK_DOWN: // 下移 ball_yvel = ball_speed; case SDLK_LEFT: // 左移 ball_xvel = -ball_speed; case SDLK_RIGHT: // 右移 ball_xvel = ball_speed; case SDLK_EQUALS: // 增加球速 ball_speed_inc = 1; case SDLK_MINUS: // 減低球速 ball_speed_inc = -1;

22 case SDLK_HOME: // 回歸中央
ball_x = (SCREEN_WIDTH - BALL_WIDTH) /2; ball_y = (SCREEN_HEIGHT - BALL_HEIGHT) /2; update_screen(); SDL_Flip(screen); break; case SDLK_ESCAPE: // 結束程式 bQuit = true; }

23 當鬆開按鍵時,把按下該鍵所設的值重置成 0。
void handle_keyUp (SDLKey sym) { switch (sym) case SDLK_UP: if (ball_yvel < 0) ball_yvel = 0; break; case SDLK_DOWN: if (ball_yvel > 0) ball_yvel = 0; case SDLK_LEFT: if (ball_xvel < 0) ball_xvel = 0; case SDLK_RIGHT: if (ball_xvel > 0) ball_xvel = 0; case SDLK_EQUALS: if (ball_speed_inc > 0) ball_speed_inc = 0; case SDLK_MINUS: if (ball_speed_inc < 0) ball_speed_inc = 0; } 當鬆開按鍵時,把按下該鍵所設的值重置成 0。

24 while (!bQuit) { while(SDL_PollEvent(&event)) switch(event.type) case SDL_VIDEOEXPOSE: SDL_Flip(screen); break; case SDL_QUIT: bQuit = true; case SDL_KEYDOWN: handle_keyDown(event.key.keysym.sym); case SDL_KEYUP: handle_keyUp(event.key.keysym.sym); }

25 if (ball_speed_inc != 0) { ball_speed += ball_speed_inc; if (ball_speed < 1) ball_speed = 1; } if (ball_xvel != 0 || ball_yvel !=0) ball_x += ball_xvel; ball_y += ball_yvel; update_screen(); SDL_Flip(screen);

26 範例 把球限制在螢幕範圍內 SDL07

27 我們先撰寫底下的 C++ 函式,使得參數 x 的值限制在 min 到 max 的區間內。
inline void restrict (int &x, int min, int max) { if (x < min) x = min; else if (x > max) x = max; }

28 if (ball_xvel != 0 || ball_yvel !=0)
{ int x_old = ball_x, y_old = ball_y; ball_x += ball_xvel; ball_y += ball_yvel; restrict(ball_x, 0, SCREEN_WIDTH - BALL_WIDTH); restrict(ball_y, 0, SCREEN_HEIGHT - BALL_HEIGHT); if (ball_x != x_old || ball_y != y_old) update_screen(); SDL_Flip(screen); }

29 範例 SDL08 SDL07 更新螢幕時,需要 Blit 整個背景圖。然而,除了球移動的部分外,大部分的背景圖都沒有改變。如下圖所示,當球移到新位址後,我們只要拷貝原來被球遮蓋掉的黑色矩形區的背景圖部分即可。 球的舊位址 球的新位址

30 改寫 update_screen (),使其只更新球移動前矩形區域的背景圖部分,程式碼如下:
程式修改: 宣告一個全域變數來存放球移動前的矩形區域: SDL_Rect ball_old_rect = {0, 0, BALL_WIDTH, BALL_HEIGHT}; 改寫 update_screen (),使其只更新球移動前矩形區域的背景圖部分,程式碼如下: void update_screen (void) { SDL_Rect offset = ball_old_rect; SDL_BlitSurface(background, &ball_old_rect, screen, &offset); show_surface(ball_x, ball_y, ball); }

31 3. 修改以下的 if 區塊 if (ball_xvel != 0 || ball_yvel !=0) {
ball_old_rect.x = ball_x; ball_old_rect.y = ball_y; ball_x += ball_xvel; ball_y += ball_yvel; restrict(ball_x, 0, SCREEN_WIDTH - BALL_WIDTH); restrict(ball_y, 0, SCREEN_HEIGHT - BALL_HEIGHT); if (ball_x != ball_old_rect.x || ball_y != ball_old_rect.y) update_screen(); SDL_Flip(screen); }

32 滑鼠事件的處理 滑鼠按鈕事件 滑鼠移動事件 SDL_MOUSEBUTTONDOWN SDL_MOUSEBUTTONUP
SDL_MOUSEMOTION

33 typedef union { Uint8 type; SDL_MouseMotionEvent motion; SDL_MouseButtonEvent button; } SDL_Event; typedef struct { Uint8 type; Uint8 button; Uint8 state; Uint16 x, y; } SDL_MouseButtonEvent; typedef struct { Uint8 type; Uint8 state; Uint16 x, y; Sint16 xrel, yrel; } SDL_MouseMotionEvent;

34 滑鼠按鈕事件 typedef struct { Uint8 type; // 事件型態 Uint8 button; // 按鈕代碼
Uint8 state; // 狀態 Uint16 x, y; // 發生位置 } SDL_MouseButtonEvent; type SDL_MOUSEBUTTONDOWN 或 SDL_MOUSEBUTTONUP button SDL_BUTTON_LEFT, SDL_BUTTON_MIDDLE, SDL_BUTTON_RIGHT state SDL_PRESSED 或 SDL_RELEASED

35 範例 while (SDL_PollEvent(&event)) { switch(event.type)
case SDL_MOUSEBUTTONDOWN: swith (event.button.button) case SDL_BUTTON_LEFT: /* 左按鈕 */ break; case SDL_BUTTON_MIDDLE: /* 中按鈕 */ break; case SDL_BUTTON_RIGHT: /* 右按鈕 */ break; } break; case SDL_MOUSEBUTTONUP :

36 滑鼠移動事件 typedef struct { Uint8 type; // 事件型態 Uint8 state; // 按鈕狀態
Uint16 x, y; // 滑鼠位置 Sint16 xrel, yrel; // 滑鼠相對位置 } SDL_MouseMotionEvent; type SDL_MOUSEMOTION state SDL_PRESSED 或 SDL_RELEASED

37 範例 while (SDL_PollEvent(&event)) { switch(event.type)
case SDL_MOUSEMOTION: if (event.motion.state == SDL_PRESSED) // 讀取 event.motion.x, …, etc } break;

38 搖桿事件的處理 搖桿結構 處理搖桿的步驟

39 搖桿結構 Joystick elements: Stick Base Trigger Extra buttons
Autofire switch Throttle Hat Switch (POV Hat) Suction Cup

40 Nintendo Game Controller

41 PS2 PS3

42 XBox Xbox 360

43 羅技疾風天盾 羅技無限天盾 羅技遊戲方向盤

44 Analog Axis D-Pad Button

45 處理搖桿的步驟 在 SDL_Init() 時,加入 SDL_INIT_JOYSTICK。
用函式 SDL_NumJoysticks() 來查詢安裝的搖桿數目。 開啟特定的搖桿。 在事件迴圈中,依所需檢查搖桿事件: SDL_JoyAxisEvent SDL_JoyButtonEvent SDL_JoyHatEvent SDL_JoyBallEvent 程式結束前,關閉搖桿。

46 初始化搖桿 if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_JOYSTICK) < 0) {
fprintf(stderr, "Couldn't initialize SDL: %s\n", SDL_GetError()); exit(1); }

47 查詢搖桿 int SDL_NumJoysticks (void); 取得連接在系統上的搖桿數目。
const char *SDL_JoystickName (int index); 取得某特定搖桿的名稱。 /* Print the names of all attached joysticks */ int num_joy, i; num_joy = SDL_NumJoysticks(); printf("%d joysticks found\n", num_joy); for(i=0; i < num_joy; i++) printf("%s\n", SDL_JoystickName(i));

48 開啟搖桿 int SDL_JoystickEventState (int state); 啟動或關閉搖桿事件的偵測。
state: SDL_QUERY, SDL_ENABLE, SDL_IGNORE SDL_Joystick *SDL_JoystickOpen (int index); 開啟編號為 index 的搖桿。 SDL_Joystick *joystick0, *joystick1; SDL_JoystickEventState(SDL_ENABLE); joystick0 = SDL_JoystickOpen(0); joystick1 = SDL_JoystickOpen(1);

49 在事件迴圈中檢查搖桿事件 switch(event.type) { case SDL_JOYAXISMOTION:
/* Handle Joystick Motion */ break; case SDL_JOYBUTTONDOWN: /* Handle Joystick Button Presses */ case SDL_JOYBUTTONUP: /* Handle Joystick Button released */ case SDL_JOYBALLMOTION: /* Handle Joyball Motion */ case SDL_JOYHATMOTION: /* Handle Hat Motion */ }

50 搖桿軸的種類 數位方向鍵 (D-Pad) 軸值為 0, 1, 或 -1。 上下左右四方向 或再加對角共八方向。
類比軸 (Analog Axis) 軸值為 到 32768。 軸值不精確。 軸值與軸位之間不是線性變化。 有時需要校準 (calibrate)。 通常第 0 軸為 x 軸,第 1 軸為 y 軸。 D-Pad Analog Axis 數位/類比 切換

51 搖桿軸的事件結構 typedef union { Uint8 type; SDL_JoyAxisEvent jaxis;
} SDL_Event; typedef struct { Uint8 type; // SDL_JOYAXISMOTION Uint8 which; // 搖桿的編號 Uint8 axis; // 搖桿軸的編號 Sint16 value; // 搖桿軸的值 } SDL_JoyAxisEvent;

52 搖桿軸的數目 取得搖桿 joystick 的搖桿軸數目。
int SDL_JoystickNumAxes(SDL_Joystick *joystick); 取得搖桿 joystick 的搖桿軸數目。 SDL_Joystick *joystick; SDL_JoystickEventState(SDL_ENABLE); joystick = SDL_JoystickOpen(0); int num_axis = SDL_JoystickNumAxes(joystick); printf(“The number of axis of joystick 0 is %d\n”, num_axis);

53 處理搖桿軸事件 搖桿軸不動在中央時,其值未必是 0,所以我們用一門檻值(如 3200)來區分搖桿軸是否在中央或移出中央。
case SDL_JOYAXISMOTION: /* Handle Joystick Motion */ if (event.jaxis.which ==0 && ((event.jaxis.value < -3200) || (event.jaxis.value > 3200))) { if (event.jaxis.axis == 0) // x 軸 /* Left-right movement code goes here */ } if (event.jaxis.axis == 1) // y 軸 /* Up-Down movement code goes here */ break; 搖桿軸不動在中央時,其值未必是 0,所以我們用一門檻值(如 3200)來區分搖桿軸是否在中央或移出中央。

54 範例 SDL11:用搖桿來移動球 // const int DEFAULT_JOYSTICK = 0;
// int joystick_x_axis = 0; // int joystick_y_axis = 1; case SDL_JOYAXISMOTION: if( event.jaxis.which == DEFAULT_JOYSTICK) // If joystick 0 has moved if( event.jaxis.axis == joystick_x_axis ) // If the X axis changed // If the X axis is neutral if ((event.jaxis.value > -3200) && (event.jaxis.value < 3200)) ball_xvel = 0; else // If not, adjust the velocity ball_xvel = ( event.jaxis.value < 0 )? -1 : 1; else if ( event.jaxis.axis == joystick_y_axis ) // If the Y axis changed // If the Y axis is neutral ball_yvel = 0; else // If not, Adjust the velocity ball_yvel = (event.jaxis.value < 0)? -1 : 1; break;

55 搖桿按鈕的事件結構 typedef union { Uint8 type; SDL_JoyButtonEvent jbutton;
} SDL_Event; typedef struct { Uint8 type; // SDL_JOYBUTTONDOWN/UP Uint8 which; // 搖桿的編號 Uint8 button; // 搖桿按鈕的編號 Uint8 state; // SDL_PRESSED/SDL_RELEASED } SDL_JoyButtonEvent;

56 搖桿按鈕的數目 取得搖桿 joystick 的搖桿按鈕數目。
int SDL_JoystickNumButtons(SDL_Joystick *joystick); 取得搖桿 joystick 的搖桿按鈕數目。 SDL_Joystick *joystick; SDL_JoystickEventState(SDL_ENABLE); joystick = SDL_JoystickOpen(0); int num_buttons = SDL_JoystickNumButtons(joystick); printf(“The number of buttons of joystick 0 is %d\n”, num_buttons);

57 處理搖桿按鈕事件 case SDL_JOYBUTTONDOWN: /* Handle Joystick Button Presses */
if (event.jbutton.which == 0) // joystick 0 switch (event.jbutton.button) { case 0: /* Handle button 0 pressed*/ break; case 1: /* Handle button 1 pressed*/ break; case 2: /* Handle button 2 pressed*/ break; case 3: /* Handle button 3 pressed*/ break; } break;

58 使用者自定的事件 由使用者依所需而定的事件型態 SDL_UserEvent SDL_PushEvent

59 SDL_UserEvent typedef union { Uint8 type; SDL_UserEvent user;
} SDL_Event; 註: SDL_USEREVENT = 24 SDL_NUMEVENTS = 32 typedef struct { Uint8 type; // SDL_USEREVENT~SDL_NUMEVENTS -1 int code; // 事件編號 void *data1; // 使用提供的事件主資料 Void *data2; // 使用提供的事件次資料 } SDL_UserEvent;

60 SDL_PushEvent int SDL_PushEvent (SDL_Event *event);
SDL_Event my_event; my_event.type = SDL_USEREVENT; my_event.user.code = my_event_code; my_event.user.data1 = significant_data; my_event.user.data2 = 0; SDL_PushEvent(&my_event);

61 Action Map(動作對應) 把裝置的動作對應到高層次的遊戲動作,譬如按下  鍵代表往右移、按下空白鍵代表射擊、按下搖桿 A 鈕也代表射擊、…、等等。 動作可以對應至多種裝置的操作,如左移可以用 ← 鍵、滑鼠左移、搖桿 x 軸左移等。 不同種類的遊戲可以制定不同的預設 Action Map。 可以用對照表把實體裝置對應至動作。 可以用使用者自定事件的技巧來處理。

62 對照表 裝置動作 遊戲動作 按下  右移 按下 D 按下空白鍵 射擊 按下滑鼠左鍵 按下搖桿 A 鈕 在事件迴圈中,當偵測到裝置事件時,用對照表將其轉換成遊戲動作,然後包裝成適當的使用者自定事件加入事件佇列。 處理使用者自定事件時,我們依遊戲動作的不同而做不同的處理。

63 遊戲輸入的設計要點 需符合玩家操作的直覺。 按鍵的配置 方向 手順 不要太複雜。 支援多種輸入裝置(鍵盤、滑鼠、搖桿)
讓玩家可以依個人偏好修改裝置動作的配置方式


Download ppt "SDL – 2 事件處理 靜宜大學資訊工程系 蔡奇偉副教授 © 2007."

Similar presentations


Ads by Google