现在你已经打开了一个窗口,让我们在上面放一张图片。
注意:从现在开始,教程将只涉及源代码的关键部分。如果想看完整的程序,你必须下载完整的源码。
//启动SDL并创建窗口
bool init();
//加载媒体
bool loadMedia();
//释放媒体并关闭SDL
void close();
在第一个教程中,我们把所有的东西都放在主函数中。由于这是一个小程序,我们可以摆脱这种做法,但在真实的程序中(比如视频游戏),你希望你的代码尽可能的模块化。这意味着你希望你的代码是整齐的块,每个块都易于调试和重用。
在这里,这意味着我们用函数来处理初始化、加载媒体和关闭SDL应用程序。我们在源文件的顶部声明这些函数。
我收到很多邮件,说在C语言中调用这个函数 "close "会引起冲突,因为不支持函数重载。这也是我在本教程中使用C++的原因之一。所以这个函数被称为 "close "并不是bug。
//我们要渲染的窗口
SDL_Window* gWindow = NULL;
//窗口所包含的表面
SDL_Surface* gScreenSurface = NULL;
//我们将加载并显示在屏幕上的图像。
SDL_Surface* gHelloWorld = NULL;
这里我们声明一些全局变量。通常情况下,你要避免在大型程序中使用全局变量。我们之所以在这里这样做,是因为我们希望源代码尽可能的简单,但是在大型项目中,全局变量会使事情变得更加复杂。由于这是一个单一的源文件程序,我们不用太担心这个问题。
这里有一个新的数据类型,叫做SDL表面。SDL表面只是一种图像数据类型,它包含了图像的像素以及渲染所需的所有数据。SDL表面使用软件渲染,这意味着它使用CPU来渲染。可以渲染硬件图像,但是比较困难,所以我们先从简单的方法来学习。在以后的教程中,我们将介绍如何渲染GPU加速的图像。
我们在这里要处理的图像是屏幕图像(你在窗口内看到的)和我们将从文件中加载的图像。
请注意,这些都是指向 SDL 表面的指针。原因是:
另外,一定要记得初始化你的指针。我们在声明它们的时候会立即将它们设置为NULL。
bool init(){
//初始化标志
bool success = true;
//初始化SDL
if( SDL_Init( SDL_INIT_VIDEO ) < 0 )
{
printf( "SDL could not initialize! SDL_Error: %s\n", SDL_GetError() );
success = false;
}
else
{
//创建窗口
gWindow = SDL_CreateWindow( "SDL Tutorial", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, SCREEN_WIDTH, SCREEN_HEIGHT, SDL_WINDOW_SHOWN );
if( gWindow == NULL )
{
printf( "Window could not be created! SDL_Error: %s\n", SDL_GetError() );
success = false;
}
else
{
//获取窗口表面
gScreenSurface = SDL_GetWindowSurface( gWindow );
}
}
return success;
}
如你在这里看到的,我们已经采用了SDL初始化和窗口创建代码,并将其放在自己的函数中。新功能是调用了SDL_GetWindowSurface。
我们想在窗口内部显示图像,为了做到这一点,我们需要得到窗口内部的图像。所以我们调用SDL_GetWindowSurface来获取窗口包含的表面。
bool loadMedia(){
//加载成功标志
bool success = true;
//加载图片
gHelloWorld = SDL_LoadBMP( "02_getting_an_image_on_the_screen/hello_world.bmp" );
if( gHelloWorld == NULL )
{
printf( "Unable to load image %s! SDL Error: %s\n", "02_getting_an_image_on_the_screen/hello_world.bmp", SDL_GetError() );
success = false;
}
return success;
}
在 load media 函数中,我们使用 SDL_LoadBMP 加载图像。SDL_LoadBMP 接收 bmp 文件的路径并返回加载的表面。如果函数返回NULL,意味着它失败了,所以我们使用SDL_GetError向控制台打印一个错误。
需要注意的是,这段代码假设你的工作目录中有一个名为 "02_getting_an_image_on_the_screen "的目录,其中包含一个名为 "hello_world.bmp "的图片。工作目录是你的应用程序认为它正在运行的地方。通常情况下,你的工作目录是你的可执行文件所在的目录,但有些程序,如Visual Studio,会将工作目录改为vcxproj文件所在的目录。所以,如果你的程序找不到图像,请确保它在正确的地方。
同样,如果程序正在运行,但它无法加载镜像,你可能有一个工作目录的问题。工作目录的功能因操作系统和IDE而异。如果上网搜索如何找到或修复工作目录都找不到解决办法,我建议把 "02_getting_an_image_on_thescreen "文件夹里的 "hello_world.bmp "挪来挪去,直到程序最终能加载它。
void close(){
//释放表面
SDL_FreeSurface( gHelloWorld );
gHelloWorld = NULL;
//销毁窗口
SDL_DestroyWindow( gWindow );
gWindow = NULL;
//退出SDL子系统
SDL_Quit();
}
在我们的清理代码中,我们像之前一样销毁窗口并退出 SDL,但我们还必须处理我们加载的表面。我们通过SDL_FreeSurface来释放它。不要担心屏幕表面,SDL_DestroyWindow会处理它。
当你的指针没有指向任何东西的时候,一定要养成让它们指向NULL的习惯。
int main( int argc, char* args[] ){
//启动SDL并创建窗口
if( !init() )
{
printf( "Failed to initialize!\n" );
}
else
{
//加载媒体
if( !loadMedia() )
{
printf( "Failed to load media!\n" );
}
else
{
//应用图像
SDL_BlitSurface( gHelloWorld, NULL, gScreenSurface, NULL );
//更新表面
SDL_UpdateWindowSurface( gWindow );
//等待2秒
SDL_Delay( 2000 );
}
}
//释放资源和关闭SDL
close();
return 0;
}
在我们的主函数中,我们初始化SDL并加载图像。如果加载成功,我们就使用 SDL_BlitSurface 将加载的表面混合到屏幕表面。
blitting的作用是将一个源表面和一个拷贝标记到目标表面上。SDL_BlitSurface的第一个参数是源图像。第三个参数是目标图像。我们将在以后的教程中关注第二个和第四个参数。
现在,如果这是我们唯一的绘图代码,我们仍然不会在屏幕上看到我们加载的图像。还有一个步骤。
在屏幕上绘制了所有我们要显示的这一帧画面后,我们要使用SDL_UpdateWindowSurface来更新屏幕。当你画到屏幕上的时候,一般不是画到你所能看到的屏幕图像上。默认情况下,大部分的渲染系统都是双缓冲的。这两个缓冲区就是前缓冲区和后缓冲区。
当你进行SDL_BlitSurface这样的绘制调用时,你会渲染到后缓冲区。你在屏幕上看到的是前缓冲区。我们这样做的原因是因为大多数帧需要将多个对象绘制到屏幕上。如果我们只有一个前缓冲区,我们将能够看到正在绘制的帧,这意味着我们将看到未完成的帧。所以我们要做的是先把所有的东西都画到后面的缓冲区,一旦我们完成了,我们就把后面和前面的缓冲区交换一下,这样现在用户就可以看到完成的帧了。
这也意味着你不会在每次blit之后调用SDL_UpdateWindowSurface,只有在当前帧的所有blits都完成之后才会调用。
现在我们已经把所有的东西都渲染到窗口上了,我们延迟两秒钟,这样窗口就不会消失了。等待结束后,我们关闭程序。
在 这里[1]下载本教程的媒体和源代码。
参考资料
[1]
这里: http://www.lazyfoo.net/tutorials/SDL/02_getting_an_image_on_the_screen/02_getting_an_image_on_the_screen.zip
[2]
原文链接: http://www.lazyfoo.net/tutorials/SDL/02_getting_an_image_on_the_screen/index.php