在前面學習SDL的例子運行時,我們發現我們的窗口只停留了幾秒,但是如果設置更長時間顯然也有其他的弊端。
那么有沒有一種好的辦法可以解決這個問題呢?例如:能不能讓窗口一直顯示,直到檢測到用戶用鼠標點擊關閉按鈕后才關閉呢?
答:顯然可以! 下面就來介紹一下SDL的事件處理機制。
1. SDL 事件處理機制原理
SDL事件就是鍵盤事件,鼠標事件,窗口事件等。SDL將所有事件都存放在一個隊列中。所有對事件的操作,其實就是對隊列的操作。
而SDL對這些事件都做了封裝,提供了統一的API,下面我們就來詳細的看一下。
2. SDL 操作事件隊列的API
- SDL_PollEvent: 將隊列頭中的事件拋出來。
- SDL_WaitEvent: 當隊列中有事件時,拋出事件。否則處於阻塞狀態,釋放 CPU。
- SDL_WaitEventTimeout: 與SDL_WaitEvent的區別時,當到達超時時間后,退出阻塞狀態。
- SDL_PeekEvent: 從隊列中取出事件,但該事件不從隊列中刪除。
- SDL_PushEvent: 向隊列中插入事件。
3. SDL 處理事件的API
- SDL_WindowEvent : Window窗口相關的事件。
- SDL_KeyboardEvent : 鍵盤相關的事件。
- SDL_MouseMotionEvent : 鼠標移動相關的事件。
- SDL_QuitEvent : 退出事件。
- SDL_UserEvent : 用戶自定義事件。
實戰
在上面我們也說過了,如果不做SDL窗口的關閉事件的處理,我們是不能夠通過點擊關閉按鈕,關閉SDL顯示的窗口的。這樣對用戶是非常不友好的。
下面我們對SDL的Hello World代碼做一下優化,其實就是在程序的末尾增加SDL_Event的事件處理,本例做的事情是檢測用戶是否按下了退出按鈕。如果檢測到了,則直接退出,否則保持顯示狀態。
代碼實例:
// SDL.cpp : 此文件包含 "main" 函數。程序執行將在此處開始並結束。 // #include "pch.h" #include <iostream> extern "C" { #include "SDL.h" } int main(int argc, char* argv[]) { if (SDL_Init(SDL_INIT_VIDEO)) { std::cout << "SDL_Init Error: " << SDL_GetError() << std::endl; return 1; } SDL_Window *win = SDL_CreateWindow("Hello World!", 100, 100, 640, 480, SDL_WINDOW_SHOWN); if (win == nullptr) { std::cout << "SDL_CreateWindow Error: " << SDL_GetError() << std::endl; return 1; } SDL_Renderer *ren = SDL_CreateRenderer(win, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC); if (ren == nullptr) { SDL_DestroyWindow(win); std::cout << "SDL_CreateRender Error: " << SDL_GetError() << std::endl; SDL_Quit(); return 1; } std::string imagePath = "1.bmp"; SDL_Surface *bmp = SDL_LoadBMP(imagePath.c_str()); if (bmp == nullptr) { SDL_DestroyRenderer(ren); SDL_DestroyWindow(win); std::cout << "SDL_LoadBMP Error: " << SDL_GetError() << std::endl; SDL_Quit(); return 1; } SDL_Texture *tex = SDL_CreateTextureFromSurface(ren, bmp); SDL_FreeSurface(bmp); if (tex == nullptr) { SDL_DestroyRenderer(ren); SDL_DestroyWindow(win); std::cout << "SDL_CreateTextureFromSurface Error: " << SDL_GetError() << std::endl; SDL_Quit(); return 1; } for (int i = 0; i < 3; ++i) { SDL_RenderClear(ren); SDL_RenderCopy(ren, tex, NULL, NULL); SDL_RenderPresent(ren); SDL_Delay(1000); } int quit = 1; do { SDL_Event event; SDL_WaitEvent(&event); switch (event.type) { case SDL_QUIT: SDL_Log("Event type is %d", event.type); quit = 0; default: SDL_Log("Event type is %d", event.type); break; } } while (quit); SDL_DestroyTexture(tex); SDL_DestroyRenderer(ren); SDL_DestroyWindow(win); SDL_Quit(); // Return return 0; }
運行效果:
我們能看到同HelloWorld一樣的界面輸出,此時如果我們對SDL窗口不做任何處理的話,界面是不會消失的,當我們點擊窗口的關閉按鈕后,界面關閉了。
4. SDL_PollEvent 與 SDL_WaitEvent
細心的人會發現,使用 SDL_PollEvent 和使用 SDL_WaitEvent 兩個方法都能處理SDL的事件隊列。如果我們簡單的將程序中的SDL_WaitEvent 替換為SDL_PollEvent ,運行時發現也沒什么問題。但是當我們打開任務管理器時,發現我們的程序居然跑滿了CPU。是什么原因造成的呢?我們來仔細看一下我們增加的代碼吧。它由兩層 while 循環組成,最里面的while循環的意思是,當隊列中一直能取出事件,那就讓他一直做下去,直到事件隊列為空。外面的while循環的意思是,當隊列為空的時候,重新執行內部的while循環。也就是說代碼一直在工作,從不休息。所以導致CPU很快就跑滿了。 而使用SDL_WaitEvent方法,CPU就不會出現這個問題,因為當它發現隊列為空時,它會阻塞在那里,並將CPU占用釋放掉。
SDL_WaitEvent和SDL_PollEvent這兩個方法使用的場景不同:
- 對於游戲來說,它要求事件的實時處理,我們最好使用SDL_PollEvent方法
- 對於一些其它實時性不高的case來說,則可以使用 SDL_WaitEvent了
