随笔 - 96  文章 - 255  trackbacks - 0
<2008年2月>
272829303112
3456789
10111213141516
17181920212223
2425262728291
2345678

E-mail:zbln426@163.com QQ:85132383 长期寻找对战略游戏感兴趣的合作伙伴。

常用链接

留言簿(21)

随笔分类

随笔档案

SDL相关网站

我的个人网页

我的小游戏

资源下载

搜索

  •  

积分与排名

  • 积分 - 488851
  • 排名 - 37

最新评论

阅读排行榜

评论排行榜

作者:龙飞

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(
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。
posted on 2008-02-04 03:31 lf426 阅读(7973) 评论(13)  编辑 收藏 引用 所属分类: SDL入门教程

FeedBack:
# re: SDL入门教程(三):1、如何实现按下ESC退出程序? 2008-04-16 11:39 WANGYY
请问一下,为什么atexit(SDL_Quit)在窗口创建之前啊?那不是把SDL退出了,还创建窗口等这些事情有什么用呢?
我试过,把atexit(SDL_Quit)放在最后退出窗口之后跟楼主那样是一样的效果
但就是不明白为什么,请指教,谢谢!  回复  更多评论
  
# re: SDL入门教程(三):1、如何实现按下ESC退出程序?[未登录] 2008-04-16 11:57 lf426
atexit()的效果相当于在main()终止的时候执行,无论是正常退出,比如return 0,或者异常退出,比如exit (-1)或者其他异常的情况下,只要main()结束了,atexit()就会调用,放在main()的任何位置理论上都是可以的。
不过后面的章节我使用对象来管理SDL的装载与退出了,就用不上atexit()了。  回复  更多评论
  
# re: SDL入门教程(三):1、如何实现按下ESC退出程序? 2009-03-25 15:03 hello
SDL_Event gameEvent;
SDL_PollEvent(&gameEvent);
if ( &gameEvent != 0 ){
...
}

这里&gameEvent永远也不会为空啊  回复  更多评论
  
# re: SDL入门教程(三):1、如何实现按下ESC退出程序? 2009-03-26 15:04 LHNing
@hello
应该是你调试的假象吧  回复  更多评论
  
# re: SDL入门教程(三):1、如何实现按下ESC退出程序?[未登录] 2009-04-02 11:19 haha
@LHNing

SDL_PollEvent(&gameEvent);

人家hello是说

不管你在这个函数里做什么, 你也不可能改变gameEvent这个局部变量的内存地址, 即 &gameEvent 是不会变的!

扯什么什么假象啊....
  回复  更多评论
  
# re: SDL入门教程(三):1、如何实现按下ESC退出程序? 2009-04-28 11:07 liigo
思路很条理,表达很清晰  回复  更多评论
  
# re: SDL入门教程(三):1、如何实现按下ESC退出程序? 2009-06-19 10:22 akira
if ( &gameEvent != 0 )


写出这样的代码页好意思拿出来啊?笑  回复  更多评论
  
# re: SDL入门教程(三):1、如何实现按下ESC退出程序? 2009-07-19 18:23 林某
面向对象!C++是面向对象的语言,这么写程序不伦不类。  回复  更多评论
  
# re: SDL入门教程(三):1、如何实现按下ESC退出程序? 2009-08-16 20:07 路过
SDL_PollEvent(&gameEvent);
if ( &gameEvent != 0 ){

估计作者的原意是:
if (SDL_PollEvent(&gameEvent) != 0 ){

因为后面的解释是按照后者的
估计作者犯了低级错误而已
  回复  更多评论
  
# re: SDL入门教程(三):1、如何实现按下ESC退出程序?[未登录] 2009-10-07 09:47 xosen
我们分析两种情况下的模型。使用if的函数,实际上每次循环只查询event一次;而使用while的时候,内嵌的while循环会一直对“悬而未解”的事件队列(event queue)“弹”(这个隐喻意味着某个event一旦被处理,就不再属于queue的一个元素)出的头值进行处理,直到事件队列为空。这似乎更符合理想的模型。但是实际上,计算机的效率并不是我们假设的那样低,event queue在一个外循环期间,始终保持两种状态:要么为空,要么最多一个。换句话说,你不可能在一次gameOver==false的循环期间,为event queue挤进去1个之上(>1)的event,这就是if与while效果相同的原因。

用if和while是完全不一样的处理方式  回复  更多评论
  
# re: SDL入门教程(三):1、如何实现按下ESC退出程序? 2009-11-05 21:34 metin9527
没错,if和while是完全不同的,if可以使后面的doSomeLoopThings()每次循环得到执行,如果使用while poll事件的话,如果事件不断,后面的doSomeLoopThings()会得不到执行!  回复  更多评论
  
# re: SDL入门教程(三):1、如何实现按下ESC退出程序? 2013-05-03 15:47 匿名
@akira
这么写没错,运行正常  回复  更多评论
  
# re: SDL入门教程(三):1、如何实现按下ESC退出程序? 2013-12-16 16:17 许思维
@hello同学说的对,if ( &gameEvent != 0 )永远也不会成立。
楼主@lf426,你的最后一句:
“你不可能在一次gameOver==false的循环期间,为event queue挤进去1个之上(>1)的event,这就是if与while效果相同的原因。”
你的这句话不正确。也与你倒数第二句“计算机的效率并不是我们假设的那样低”有冲突。你这两句话是不是打错了,请你仔细检查?
实际上,一个gameOver==false循环期间,event queue很可能被置入多于一个event。因为就一般情况而言,doSomeLoopThings执行所需的时间是未知的,可能比例子中的一个输出多的多,event queue必定会捕捉在此期间的所有事件,当然不会只有1个。
在doSomeLoopThings中加入延时(SDL_Delay(1000))即可验证:
void doSomeLoopThings()
{
std::cout << ".";
SDL_Delay(500);
return;
}
// 再对while(gameOver==false)略作修改:
while( gameOver == false ){
SDL_Event gameEvent;
int ecount = 0;
while ( SDL_PollEvent(&gameEvent) != 0 ){
ecount++;
if ( gameEvent.type == SDL_QUIT ){
gameOver = true;
}
if ( gameEvent.type == SDL_KEYUP ){
if ( gameEvent.key.keysym.sym == SDLK_ESCAPE ){
gameOver = true;
}
}
}
std::cout << "processed " << ecount << " events!\n";
doSomeLoopThings();
}
  回复  更多评论
  

只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理