SDL入門教程(三):1、如何實現按下ESC退出程序?

作者:龍飛

1.1:遊戲中的退出習慣。

        如同我們經常遇到的遊戲,一般想退出的時候,我們會習慣性的按下ESC——即使遊戲不會馬上退出,也一般會調出一個帶有退出選擇的菜單。我們希望修房子的時候,最好先計劃在哪裏修門,所以,我認爲應該優先掌握“退出遊戲”的方法。簡單的說,我們啓動了一個SDL程序,我們希望按下ESC就能退出,怎麼實現?

1.2:事件(event)查詢初探。

        在計算機科學領域,隱喻無處不見。所有的抽象概念,若不是被很好的用形象概念或者已經被理解的抽象概念去解釋,其本身很難讓人們明白是什麼。事件,在這裏指的就是計算機所直接感知到的玩家對於其的作用。比如你按下某個鍵,又鬆開,移動了鼠標等等。所有的這些行爲都被稱爲事件。在計算機看來,任何事件的發生都是有先後的(如果你瞭解相對論,你就會知道其實世界上任意兩點間並不存在“同時”的概念)。如果計算機工作效率很低,這些事件就會排着隊列等待接受處理——這裏又用到一個模型的隱喻——隊列(queue),這是計算機算法與數據結構知識中很重要的概念,往往意味着其特徵是“先進先出”。
        我們就從這個事件隊列(event queue)的模型去思考吧。要知道,隊列是有可能爲空的,這就如同銀行的服務窗口前面不會總是有人排隊一樣——當然,我假設你不是總呆在北京,也去過一些小城市^^,那麼,沒有客戶在窗口前需要被服務的時候,這個窗口的工作人員應該是什麼狀態呢?至少有兩種不同的等待方式:一種是什麼也不做傻等;一種是邊喝點茶看看報紙算算賬什麼的其他事情邊等待。對於計算機來說,第一種停下來等,就是wait;第二種繼續做其他事情的同時等,就是poll。前者一看就明白,後者被不知道某位前輩高人翻譯成“輪詢”,好吧,說句實話,我笨,光看“輪詢”這個詞,完全無法理解是什麼意思-_-!!!
        SDL爲我們提供了兩種等待事件的方式:
int SDL_WaitEvent(SDL_Event *event);
int SDL_PollEvent(SDL_Event *event);

        兩個函數的返回值都是int,形參是SDL事件結構(C++裏面,就把結構看成類吧。)指針SDL_Event*(請注意我把SDL_Event和*連着寫,這意味着在認識上,我把SDL_Event*本身看成一種複合類類型。)我們知道,在C/C++裏面,函數至少有三種基本功能。1、像命令似的起了某種作用;2、通過計算得到我們需要的返回值;3、指針(C++裏面的引用)參數也可以傳值。這裏,我們先忽略SDL_Event結構的構造,看看這兩個函數的作用。首先,他們不會引起某種作用;其次,他們的返回值是1或者0;最後,他們會通過指針參數傳值,這是重點。
        早期的C裏面,是沒有關鍵字true和false的。通常用1代表true,0代表false。我個人覺得,在SDL裏面,1和0的概念最容易與-1和0的概念混淆。我們在學習SDL_Init的時候,說到返回值0代表成功,-1代表失敗。其實細細想,還是有差別的。0和1是計算機固有的數據表示方式,而-1是計算機原始方式所無法理解的,所以會代表着異常。所以,在異常退出的時候,我選擇使用return -1。
        這兩個函數的返回值,在等到了事件的時候,返回1,否則返回0。官方文檔裏面用類似while(SDL_PollEvent(&event))的方法引導輪詢機制的開始,但是我覺得,對於新手來說,這樣的表述不是很直觀。與其間接的問窗口的服務員有客戶來嗎,還不如自己直接看看有沒客戶(event queue是不是爲空)。所以,在後面的例子裏面,我實際上用的是if ( &event != 0 )。

1.3:當前窗口。

        如果你有興趣研究SDL的官方文檔,看到事件介紹(Introduction to Events)部分,也許會對以下問題感覺到奇怪:SDL的事件查詢機制是與SDL_INIT_VIDEO同時裝載的。爲什麼呢?
        我們知道,我們開發的遊戲實際上是運行在操作系統的平臺上的。當前的操作系統,都是多任務的操作系統。具體說到GUI,有個很重要的概念就是你目前操作的是哪個程序,也就是更形象的概念——當前窗口。有些event可能是各個窗口,甚至包括系統本身共享的,比如鼠標移動(這不是絕對的,只是有可能);有些event只會被當前窗口接受,就如同你不會希望同時開着兩個Word文件在編輯,修改一個文件的時候,另外一個也被無情的修改了。所以,SDL程序運行的時候,只有指定了哪個窗口是這個程序的窗口,並且這個窗口是當前窗口的時候,大部分event才能被正確的響應。
        注意,console窗口不是SDL程序的運行窗口,它屬於操作系統本身。我們要打開SDL的程序窗口,需要引入一個新函數:

SDL_Surface *SDL_SetVideoMode(int width, int height, int bitsperpixel, Uint32 flags);
        我們這裏僅僅是爲了打開SDL的程序窗口來引入這個函數,只做個簡單介紹:1、這個函數本身有作用——打開SDL程序窗口;2、前三個參數分別是這個打開窗口的寬,高和位深,最後那個flags我們這裏只介紹SDL_SWSURFACE,這個位標表示把返回值的數據建立在系統內存裏面。

1.4:一段演示按下ESC(或者點x)退出SDL窗口的程序。
///////////////////
//按下ESC(或者點x)退出SDL窗口
//聯繫我: [email protected]
//再別流年的技術實驗室
//http://www.cppblog.com/lf426/
///////////////////

#include 
<iostream>
#include 
"SDL/SDL.h"

void pressESCtoQuit();
void doSomeLoopThings();

int main(int argc,char* argv[])
{
    
try {
        
if ( SDL_Init(SDL_INIT_VIDEO == -1 ))
            
throw SDL_GetError();
    }
    
catch ( const char* s ) {
        std::cerr 
<< s << std::endl;
        
return -1;
    }
    atexit(SDL_Quit);

    SDL_SetVideoMode(
64048032, SDL_SWSURFACE);
    std::cout 
<< "Program is running, press ESC to quit./n";
    pressESCtoQuit();
    std::cout 
<< "GAME OVER" << std::endl;

    
return 0;
}

void pressESCtoQuit()
{
    std::cout 
<< "pressESCtoQuit() function begin/n";
    
bool gameOver = false;
    
while( gameOver == false ){
        SDL_Event gameEvent;
        SDL_PollEvent(
&gameEvent);
        
if ( &gameEvent != 0 ){
            
if ( gameEvent.type == SDL_QUIT ){
                gameOver 
= true;
            }
            
if ( gameEvent.type == SDL_KEYDOWN ){
                
if ( gameEvent.key.keysym.sym == SDLK_ESCAPE ){
                    gameOver 
= true;
                }
            }
        }
        doSomeLoopThings();
    }
    
return;
}

void doSomeLoopThings()
{
    std::cout 
<< ".";
    
return;
}

1.5:兩個細節問題。

        我們修改pressESCtoQuit()函數兩個小地方:
void pressESCtoQuit()
{
    std::cout 
<< "pressESCtoQuit() function begin/n";
    
bool gameOver = false;
    
while( gameOver == false ){
        SDL_Event gameEvent;
        
while ( SDL_PollEvent(&gameEvent) != 0 ){
            
if ( gameEvent.type == SDL_QUIT ){
                gameOver 
= true;
            }
            
if ( gameEvent.type == SDL_KEYUP ){
                
if ( gameEvent.key.keysym.sym == SDLK_ESCAPE ){
                    gameOver 
= true;
                }
            }
        }
        doSomeLoopThings();
    }
    
return;
}
        我們把引起輪詢機制的if換成了while。我們前面說過,查詢&gameEvent是不是爲空,是爲了強調SDL_PollEvent()使用指針參數傳了值。而事實上,if是判斷,while不僅僅是判斷,而且還是循環。但是更換這兩個關鍵字似乎並沒有出現不同,爲什麼呢?
        我們分析兩種情況下的模型。使用if的函數,實際上每次循環只查詢event一次;而使用while的時候,內嵌的while循環會一直對“懸而未解”的事件隊列(event queue)“彈”(這個隱喻意味着某個event一旦被處理,就不再屬於queue的一個元素)出的頭值進行處理,直到事件隊列爲空。這似乎更符合理想的模型。但是實際上,計算機的效率並不是我們假設的那樣低,event queue在一個外循環期間,始終保持兩種狀態:要麼爲空,要麼最多一個。換句話說,你不可能在一次gameOver==false的循環期間,爲event queue擠進去1個之上(>1)的event,這就是if與while效果相同的原因。
        我們修改的第二個細節是把SDL_KEYDOWN換成了SDL_KEYUP,這是爲了提醒大家,按下某個鍵和按下某個鍵再鬆開,是兩種不同的event。  
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章