【C++SDL2中文教程】二,在屏幕上获取图像

//启动SDL并创建窗口
bool init();

//加载媒体
bool loadMedia();

//释放媒体并关闭SDL
void close();

在第一个教程中,我们将所有内容都放在了 main 函数中。由于它是一个小程序,我们可以摆脱它,但在实际程序(如视频游戏)中,您希望您的代码尽可能模块化。这意味着您希望您的代码整齐地排列成易于调试和重用的代码块。

这意味着我们有处理初始化、加载媒体和关闭 SDL 应用程序的函数。我们在源文件顶部附近声明这些。

我收到很多关于在 C 中调用此函数“关闭”如何导致冲突的电子邮件,因为它不支持函数重载。这是我在本教程中使用 C++ 的原因之一。所以这个函数被称为“关闭”并不是一个错误。

//我们将渲染到的窗口
SDL_Window* gWindow = NULL;
    
//窗口的表面
SDL_Surface* gScreenSurface = NULL;

//我们将加载并显示在屏幕上的图像
SDL_Surface* gHelloWorld = NULL;


这里我们声明一些全局变量。通常,您希望避免在大型程序中使用全局变量。我们在这里这样做的原因是因为我们希望源代码尽可能简单,但在大型项目中,全局变量会使事情变得更加复杂。由于这是一个单一的源文件程序,我们不必太担心它。

这是一种称为 SDL Surface 的新数据类型。SDL 表面只是一种图像数据类型,它包含图像的像素以及渲染它所需的所有数据。SDL 表面使用软件渲染,这意味着它使用 CPU 进行渲染。渲染硬件图像是可能的,但它有点困难,所以我们将首先以简单的方式学习它。在以后的教程中,我们将介绍如何渲染 GPU 加速图像。

我们将在这里处理的图像是屏幕图像(您在窗口内看到的)和我们将从文件加载的图像。

请注意,这些是指向 SDL 曲面的指针。原因是 1) 我们将动态分配内存来加载图像,以及 2) 最好按内存位置引用图像。想象一下,你有一个游戏,它的砖墙由多次渲染的相同砖块图像组成(如超级马里奥兄弟)。当您可以拥有一个图像副本并一遍又一遍地渲染它时,在内存中拥有数十个图像副本是很浪费的。

另外,永远记得初始化你的指针。我们在声明它们时立即将它们设置为 NULL。

bool init()
{
    //初始化标志
    bool success = true;

    //初始化SDL
    if( SDL_Init( SDL_INIT_VIDEO ) < 0 )
    {
        printf( "SDL无法初始化!SDL_错误: %s\n", SDL_GetError() );
        success = false;
    }
    else
    {
        //创建窗口
        gWindow = SDL_CreateWindow( "SDL教程", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, SCREEN_WIDTH, SCREEN_HEIGHT, SDL_WINDOW_SHOWN );
        if( gWindow == NULL )
        {
            printf( "无法创建窗口!SDL_错误: %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( "无法加载图像%s!SDL错误: %s\n", "02_getting_an_image_on_the_screen/hello_world.bmp", SDL_GetError() );
        success = false;
    }

    return success;
}


在加载媒体函数中,我们使用 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_the_screen”文件夹中移动“hello_world.bmp”,直到程序最终可以加载它。

void close()
{
    //释放表面
    SDL_FreeSurface( gHelloWorld );
    gHelloWorld = NULL;

    //退出窗口
    SDL_DestroyWindow( gWindow );
    gWindow = NULL;

    //退出SDL子系统
    SDL_Quit();
}


在我们的清理代码中,我们像以前一样销毁窗口并退出 SDL,但我们还必须处理加载的表面。我们通过使用 SDL_FreeSurface 释放它来做到这一点。不用担心屏幕表面,SDL_DestroyWindow 会处理它。

确保养成让指针在不指向任何内容时指向 NULL 的习惯。

为什么我们要费心从一个无论如何都会终止的程序中释放资源?当程序返回时,资源不会被释放吗?

答案是:我不知道。根据您的操作系统,它可能会也可能不会。欢迎来到 C++ 中未定义行为的世界。一般规则是,如果你能避免未定义的行为,你就避免它。这是未定义行为的一个相当良性的示例,但我看到工程师花费数天时间寻找由未定义行为引起的错误(顺便说一句,不要在构造函数中调用虚函数)。现在养成认真对待未定义行为的习惯,因为您不想在截止日期紧迫的团队项目中处理它。

int main( int argc, char* args[] )
{
    //启动SDL并创建窗口
    if( !init() )
    {
        printf( "初始化失败!\n" );
    }
    else
    {
        //加载媒体
        if( !loadMedia() )
        {
            printf( "加载媒体失败!\n" );
        }
        else
        {
            //应用图像
            SDL_BlitSurface( gHelloWorld, NULL, gScreenSurface, NULL );


在我们的主函数中,我们初始化 SDL 并加载图像。如果成功,我们使用 SDL_BlitSurface 将加载的表面blit 到屏幕表面上。

blitting 所做的是获取源表面并将其副本标记到目标表面上。SDL_BlitSurface 的第一个参数是源图像。第三个参数是目的地。我们将在以后的教程中关注第二个和第四个参数。

现在,如果这是我们唯一的绘图代码,我们仍然看不到我们在屏幕上加载的图像。还有一步。

            //更新曲面
            SDL_UpdateWindowSurface( gWindow );


在屏幕上绘制了我们要为此帧显示的所有内容后,我们必须使用 SDL_UpdateWindowSurface 更新屏幕。当您绘制到屏幕上时,您通常不会绘制到您看到的屏幕上的图像。默认情况下,大多数渲染系统都是双缓冲的。这两个缓冲区是前缓冲区和后缓冲区。

当你进行像 SDL_BlitSurface 这样的绘制调用时,你会渲染到后台缓冲区。您在屏幕上看到的是前端缓冲区。我们这样做的原因是因为大多数帧需要在屏幕上绘制多个对象。如果我们只有一个前端缓冲区,我们将能够看到正在绘制的帧,这意味着我们会看到未完成的帧。所以我们要做的是首先将所有内容绘制到后台缓冲区,一旦完成,我们就交换后台缓冲区和前台缓冲区,以便用户现在可以看到完成的帧。

这也意味着您不会在每个 blit 之后调用 SDL_UpdateWindowSurface,只有在当前帧的所有 blit 完成之后。

            //等两秒钟
            SDL_Delay( 2000 );
        }
    }

    //释放资源并关闭SDL
    close();

    return 0;
}


现在我们已经将所有内容都渲染到了窗口,我们延迟了两秒钟,这样窗口就不会消失。等待完成后,我们关闭程序。 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值