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。  
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值