如同我们经常遇到的游戏,一般想退出的时候,我们会习惯性的按下ESC——即使游戏不会马上退出,也一般会调出一个带有退出选择的菜单。我们希望修房子的 时候,最好先计划在哪里修门,所以,我认为应该优先掌握“退出游戏”的方法。简单的说,我们启动了一个SDL程序,我们希望按下ESC就能退出,怎么实 现?
1.2:事件(event)查询初探。
在计算机科学领域,隐喻无处不见。所有的抽象概念,若不是被很好的用形象概念或者已经被理解的抽象概念去解释,其本身很难让人们明白是什么。事件,在这里 指的就是计算机所直接感知到的玩家对于其的作用。比如你按下某个键,又松开,移动了鼠标等等。所有的这些行为都被称为事件。在计算机看来,任何事件的发生 都是有先后的(如果你了解相对论,你就会知道其实世界上任意两点间并不存在“同时”的概念)。如果计算机工作效率很低,这些事件就会排着队列等待接受处理 ——这里又用到一个模型的隐喻——队列(queue),这是计算机算法与数据结构知识中很重要的概念,往往意味着其特征是“先进先出”。
我们就从这个事件队列(event queue)的模型去思考吧。要知道,队列是有可能为空的,这就如同银行的服务窗口前面不会总是有人排队一样——当然,我假设你不是总呆在北京,也去过一 些小城市^^,那么,没有客户在窗口前需要被服务的时候,这个窗口的工作人员应该是什么状态呢?至少有两种不同的等待方式:一种是什么也不做傻等;一种是 边喝点茶看看报纸算算账什么的其他事情边等待。对于计算机来说,第一种停下来等,就是wait;第二种继续做其他事情的同时等,就是poll。前者一看就 明白,后者被不知道某位前辈高人翻译成“轮询”,好吧,说句实话,我笨,光看“轮询”这个词,完全无法理解是什么意思-_-!!!
SDL为我们提供了两种等待事件的方式:
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的程序窗口,需要引入一个新函数:
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()函数两个小地方:
{
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的函数,实际上每次循环只查询event一次;而使用while的时候,内嵌的while循环会一直对“悬而未解”的 事件队列(event queue)“弹”(这个隐喻意味着某个event一旦被处理,就不再属于queue的一个元素)出的头值进行处理,直到事件队列为空。这似乎更符合理想 的模型。但是实际上,计算机的效率并不是我们假设的那样低,event queue在一个外循环期间,始终保持两种状态:要么为空,要么最多一个。换句话说,你不可能在一次gameOver==false的循环期间,为 event queue挤进去1个之上(>1)的event,这就是if与while效果相同的原因。
我们修改的第二个细节是把SDL_KEYDOWN换成了SDL_KEYUP,这是为了提醒大家,按下某个键和按下某个键再松开,是两种不同的event。