注: 本教程翻译自 lazyfoo的教程,原教程戳这里,可能语言不准确,本翻译仅供参考,建议对比阅读。
按键
点击窗口的关闭按钮(X)关闭程序仅仅是SDL能处理的事件的一种。另一种被游戏广泛使用的输入就是键盘输入。在这篇教程中我们将通过按下不同的按键来让不同的图像展示在屏幕上。
//枚举常量来表示按键对应的surface
enum KeyPressSurfaces
{
KEY_PRESS_SURFACE_DEFAULT,
KEY_PRESS_SURFACE_UP,
KEY_PRESS_SURFACE_DOWN,
KEY_PRESS_SURFACE_LEFT,
KEY_PRESS_SURFACE_RIGHT,
KEY_PRESS_SURFACE_TOTAL
};
在代码的的顶部我们声明了对将要用到的不同的surface的枚举。枚举就是一种更加简便地定义一堆常量的方式,这样做的好处就是不用再
const int KEY_PRESS_SURFACE_DEFAULT = 0;
const int KEY_PRESS_SURFACE_UP = 1;
const int KEY_PRESS_SURFACE_DOWN = 2;
这样一条一条地定义了。
它们默认从0开始以1递增地给枚举中每一个常量赋值,这就是说上面的代码中的
KEY_PRESS_SURFACE_DEFAULT = 0
,
KEY_PRESS_SURFACE_UP = 1
,
KEY_PRESS_SURFACE_DOWN = 2
,
KEY_PRESS_SURFACE_LEFT = 3
,
KEY_PRESS_SURFACE_RIGHT = 4
,
KEY_PRESS_SURFACE_TOTAL = 5
直接给枚举的常量赋值也可以,但是这里不会对它进行详细讲解。想了解更多的话直接谷歌即可。
编程初学者常有的一个坏习惯就是使用随意的数字而不是符号化的常量。比如他们会用1表示主菜单,2表示选项等等,对少量代码的程序来说还行,但当你面对上千行代码(游戏常有)的时候,if( option == 1 )
这样的语句会比if( option == MAIN_MENU )
更让人头疼。
//函数功能:初始化SDL并创建窗口
bool init();
//函数功能:加载多媒体文件
bool loadMedia();
//函数功能:释放多媒体文件并关闭SDL
void close();
//加载不同的图像
SDL_Surface* loadSurface( std::string path );
//将要渲染的窗口
SDL_Window* gWindow = NULL;
//窗口包含的surface
SDL_Surface* gScreenSurface = NULL;
//用以存储不同按键对应的不同图像的surface指针数组
SDL_Surface* gKeyPressSurfaces[ KEY_PRESS_SURFACE_TOTAL ];
//当前屏幕上显示的图像
SDL_Surface* gCurrentSurface = NULL;
这段代码除了与之前相同的函数外还有一个新的函数loadSurface()
。有一个通用的原则就是如果你直接复制粘贴代码使用,你的代码很可能会隐含一些错误。相比每一次都直接复制粘贴加载文件这部分的代码到主函数中使用,我们还是选择使用函数来处理加载的问题。
在这段代码中很重要的一部分就是我们用了指向SDL surface的指针的数组gKeyPressSurface
来保存我们将要用到的所有图像在内存中的地址。我们将通过用户按下的按键来让gCurrentSurface
(它指向将被显示的图像)指向该数组中不同的图像。
SDL_Surface* loadSurface( std::string path )
{
//从给定的地址中加载图像
SDL_Surface* loadedSurface = SDL_LoadBMP( path.c_str() );
if( loadedSurface == NULL )
{
printf( "Unable to load image %s! SDL Error: %s\n", path.c_str(), SDL_GetError() );
}
return loadedSurface;
}
这就是loadSurface()
函数的定义,它将加载图像并打印相应的错误(如果出现错误了的话),它基本和之前代码中用来加载图像的代码一致,但是将这些代码放进一个函数会让加载图像以及dubug变得容易许多。
作者在这里提到:我收到很多C++新手的问题,没有这个函数就不会内存泄漏。它确实申请内存去加载一个新的SDL surface而又不释放其内存地把它返回了,但是一申请内存就立马释放内存又意义何在呢?这个函数的功能仅仅是加载surface并返回新加载的surface,所以无论是哪里调用了这个函数都可以在结束使用后释放内存。在这个程序里面,加载过的surface在close()
函数中释放内存。
bool loadMedia()
{
//保存是否成功加载状态的标志变量
bool success = true;
//加载默认的surface
gKeyPressSurfaces[ KEY_PRESS_SURFACE_DEFAULT ] = loadSurface( "04_key_presses/press.bmp" );
if( gKeyPressSurfaces[ KEY_PRESS_SURFACE_DEFAULT ] == NULL )
{
printf( "Failed to load default image!\n" );
success = false;
}
//加载 ↑ 键对应的 surface
gKeyPressSurfaces[ KEY_PRESS_SURFACE_UP ] = loadSurface( "04_key_presses/up.bmp" );
if( gKeyPressSurfaces[ KEY_PRESS_SURFACE_UP ] == NULL )
{
printf( "Failed to load up image!\n" );
success = false;
}
//加载 ↓ 键对应的 surface
gKeyPressSurfaces[ KEY_PRESS_SURFACE_DOWN ] = loadSurface( "04_key_presses/down.bmp" );
if( gKeyPressSurfaces[ KEY_PRESS_SURFACE_DOWN ] == NULL )
{
printf( "Failed to load down image!\n" );
success = false;
}
//加载 ← 键对应的 surface
gKeyPressSurfaces[ KEY_PRESS_SURFACE_LEFT ] = loadSurface( "04_key_presses/left.bmp" );
if( gKeyPressSurfaces[ KEY_PRESS_SURFACE_LEFT ] == NULL )
{
printf( "Failed to load left image!\n" );
success = false;
}
//加载 → 键对应的 surface
gKeyPressSurfaces[ KEY_PRESS_SURFACE_RIGHT ] = loadSurface( "04_key_presses/right.bmp" );
if( gKeyPressSurfaces[ KEY_PRESS_SURFACE_RIGHT ] == NULL )
{
printf( "Failed to load right image!\n" );
success = false;
}
return success;
}
在loadMedia()
函数里面加载所有将要显示到屏幕上的图像。
//主循环标志变量
bool quit = false;
//事件 handler
SDL_Event e;
//设置默认的图像为当前将要展示到屏幕的图像
gCurrentSurface = gKeyPressSurfaces[ KEY_PRESS_SURFACE_DEFAULT ];
//当程序在运行中
while( !quit )
{
在主函数中进入主循环前,我们设置默认的surface为即将展示的surface。
//按队列顺序处理事件
while( SDL_PollEvent( &e ) != 0 )
{
//如果用户点击了关闭
if( e.type == SDL_QUIT )
{
quit = true;
}
//如果用户按了按键
else if( e.type == SDL_KEYDOWN )
{
//依据相应的按键选择surface
switch( e.key.keysym.sym )
{
case SDLK_UP:
gCurrentSurface = gKeyPressSurfaces[ KEY_PRESS_SURFACE_UP ];
break;
case SDLK_DOWN:
gCurrentSurface = gKeyPressSurfaces[ KEY_PRESS_SURFACE_DOWN ];
break;
case SDLK_LEFT:
gCurrentSurface = gKeyPressSurfaces[ KEY_PRESS_SURFACE_LEFT ];
break;
case SDLK_RIGHT:
gCurrentSurface = gKeyPressSurfaces[ KEY_PRESS_SURFACE_RIGHT ];
break;
default:
gCurrentSurface = gKeyPressSurfaces[ KEY_PRESS_SURFACE_DEFAULT ];
break;
}
}
}
这里就是我们的事件循环,你可以看到之前的教程中讲过的窗口关闭事件的处理,然后我们处理了SDL_KEYDOWN事件。这个事件在你按下键盘上的按键时产生。
SDL事件内部是SDL键盘事件,其中包含按键事件的信息。SDL键盘事件中包含一个SDL Keysym,其中包含有关按键的信息。 Keysym包含按下的键的对应的SDL键码。
这段代码的作用就是为相应的按键设置相应的surface。如果你想知道其他按键的键码请参阅SDL的文件。
//应用当前指向图像
SDL_BlitSurface( gCurrentSurface, NULL, gScreenSurface, NULL );
//更新surface
SDL_UpdateWindowSurface( gWindow );
在按键事件处理完并设置完相应的surface之后,我们将其显示到屏幕上。
下载多媒体文件和源代码请戳原作者链接
注: 本教程翻译自 lazyfoo的教程,原教程戳这里,可能语言不准确,本翻译仅供参考,建议对比阅读。