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窗口
//联系我: znln426@163.com
//再别流年的技术实验室
//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(640, 480, 32, 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。

作者: landuochong   发布时间: 2010-12-27