前言
在这里记录SDL的环境基本搭建和使用,方便回忆。使用该图形库也是为了方便在没有单片机和显示模块的使用,也能对简单验证些关于图形构建或界面管理的猜想和测试,所以下述不会探讨过于深入的东西。当然,也可以通过SDL官网查看介绍。
下载
在官网看到,SDL2的最新稳定版本为2.30.11,点击跳转到其GitHub下,下载SDL2-devel-2.30.11-mingw.zip
。
CLion中
这里我是在CLion创建Demo项目,并且将下载好的SDL2-devel-2.30.11-mingw.zip
解压后放入,并且编写好CMakeLists.txt,对SDL2进行链接。具体CMake流程可以参考CMake。
SDL2_30">SDL2的基本使用
教程参考至SDL2官网维基中C++ Programming (thenumb.at),其目录 - SDL2章节下的8个基本教程,下述将基于C语言实现代码,且加上对应的注释。还有就是为了保证代码看起来清晰些,把创建是否成功的状态判断,都去掉了,默认成功创建窗口、表面、图形等对象。然后在没有看到SDL2的交互函数时,暂且先用
while(1);
,来保持绘制界面。除此之外,除了在第一个案例末尾演示内存释放流程外其它案例,也不在演示释放函数。如果觉得下述讲解的太过累赘的,可以直接看上面的原教程链接。
-
创建窗口并显示出来的基本流程:
调用
SDL_Init()
初始化SDL2,在通过SDL_CreateWindow()
创建窗口对象win,在通过SDL_GetWindowSurface
基于窗口对象创建表面对象winSurface。然后将要绘制的矩形通过SDL_FillRect()
绘制到表面对象winSurface上,最后通过SDL_UpdateWindowSurface()
更新窗口对象win,显示在界面上。在结束时,调用SDL_DestroyWindow()
和SDL_Quit()
完成释放变量和关闭SDL2。表面
:SDL 将您可以绘制的任何区域(包括加载的图像)抽象为“表面”#include <stdio.h> #include <stdlib.h> #include <SDL.h> #include <synchapi.h>int main(int argc, char* argv[]) {SDL_Init( SDL_INIT_EVERYTHING ); // 初始化SDL2所有部分SDL_Window* win = SDL_CreateWindow( "my window", 100, 100, 640, 480, SDL_WINDOW_SHOWN ); // 创建窗口SDL_Surface* winSurface = SDL_GetWindowSurface( win ); // 基于窗口创建“表面”SDL_UpdateWindowSurface( win ); // 更新窗口SDL_FillRect( winSurface, NULL, SDL_MapRGB( winSurface->format, 255, 90, 120 )); // 绘制矩形SDL_UpdateWindowSurface( win ); // 更新窗口while (1); // 卡住界面(保持窗口,)SDL_DestroyWindow( win ); // 销毁窗口win = NULL; winSurface = NULL; // 释放变量SDL_Quit(); // 关闭SDL2return 0; }
-
位图显示
该节主要讲解了位图在SDL的保存、绘制和缩放等。同时相比于第1节教程多了
SDL_Rect
结构,来控制显示位置。#include <SDL.h>SDL_Window* win; SDL_Surface* winSurface; SDL_Rect dest; SDL_Surface* image1; SDL_Surface* image2;// 显示BMP图片 void ShowBMP() {dest.x = 20; dest.y = 20;SDL_BlitSurface( image1, NULL, winSurface, &dest ); // 图片1加载到表面SDL_UpdateWindowSurface( win ); // 更新窗口while (1); // 卡住界面(保持窗口) } // 缩放BMP图片 void ScaledBMP() {dest.x = 20; dest.y = 20; dest.w = 200; dest.h = 100; // 重新设置SDL_Rect结构对象SDL_BlitScaled( image1, NULL, winSurface, &dest ); // 缩放图片SDL_UpdateWindowSurface( win ); // 更新窗口while (1); // 卡住界面(保持窗口) } // 转换表面 void ConvertSurface() {dest.x = 20; dest.y = 20;image2 = SDL_ConvertSurface( image1, winSurface->format, 0 ); // 将图片1进行转换SDL_BlitSurface( image2, NULL, winSurface, &dest ); // 图片2加载到表面SDL_FreeSurface(image1); // 图片1随后释放SDL_UpdateWindowSurface( win ); // 更新窗口while (1); // 卡住界面(保持窗口) }int main(int argc, char* argv[]) {SDL_Init( SDL_INIT_EVERYTHING ); // 初始化SDL2所有部分win = SDL_CreateWindow( "MyWindow", 100, 100, 640, 480, SDL_WINDOW_SHOWN ); // 创建窗口winSurface = SDL_GetWindowSurface( win ); // 基于窗口创建“表面”image1 = SDL_LoadBMP( "../../../../logo.bmp" ); // 加载BMP图形(相对路径)// 下述三个案例逐个解开注释查看// ShowBMP();// ScaledBMP();ConvertSurface();return 0; }
-
事件活动
在教程中该节主要讲解到了事件,如健值的输入事件,关闭事件等。且通过
SDL_Event
定义的结构对象,和SDL_PollEvent()
函数获取到事件的类型和触发健值等等。还有我怀疑,教程是不是把SDL_PollEvent
函数写成SDL_PolLEvent,这压根就没有找到。下述就健值获取事件
、退出事件
、鼠标事件
列出函数案例,至于官方不推荐的健值轮询,和自定义用户就举例了,有需要的可以调整教程详细观看,还有更多的其它事件可以浏览SDL的文档网站查看。#include <SDL.h> #include "SDL_events.h"SDL_Window* win; SDL_Surface* winSurface; SDL_Event ev;char running = 1;// 按键输入验证 // 简述:通过识别按下的'1'、'2'、'3'键来,切换纯色界面。按下'4'退出案例。 void KeyInPut() {running = 1;while( running){while ( SDL_PollEvent( &ev ) != 0 ) {switch (ev.type) {case SDL_KEYDOWN:// 健值分支switch ( ev.key.keysym.sym ) {case SDLK_KP_1:SDL_FillRect( winSurface, NULL, SDL_MapRGB( winSurface->format, 255, 0, 0 )); // 绘制红色矩形break;case SDLK_KP_2:SDL_FillRect( winSurface, NULL, SDL_MapRGB( winSurface->format, 0, 255, 0 )); // 绘制绿色矩形break;case SDLK_KP_3:SDL_FillRect( winSurface, NULL, SDL_MapRGB( winSurface->format, 0, 0, 255 )); // 绘制蓝色矩形break;case SDLK_KP_4:// 退出该案例running = 0;break;}break;}}SDL_UpdateWindowSurface( win ); // 更新窗口SDL_Delay(100);} }// 窗口退出验证 // 简述:只有在 SDL_QUIT 类型的事件下,点击'x'关闭窗口,才能得到响应关闭。 void WinClose() {running = 1;while ( running ) {// Event loopwhile ( SDL_PollEvent( &ev ) != 0 ) {switch (ev.type) {case SDL_QUIT:running = 0;break;}}SDL_Delay(100);} }// 鼠标输入 // 简述:通过按下鼠标左键、中键、右键切换纯色界面。 void MouseInput() {while ( running ) {while ( SDL_PollEvent( &ev ) != 0 ) {switch (ev.type) {case SDL_MOUSEBUTTONUP:// test buttonswitch ( ev.button.button ) {case SDL_BUTTON_LEFT: // 鼠标左键SDL_FillRect( winSurface, NULL, SDL_MapRGB( winSurface->format, 255, 255, 0 ));break;case SDL_BUTTON_RIGHT: // 鼠标右键SDL_FillRect( winSurface, NULL, SDL_MapRGB( winSurface->format, 0, 255, 255 ));break;case SDL_BUTTON_MIDDLE: // 鼠标中键SDL_FillRect( winSurface, NULL, SDL_MapRGB( winSurface->format, 255, 0, 255 ));break;}break;}}SDL_UpdateWindowSurface( win ); // 更新窗口SDL_Delay(100);} }// 退出后清理释放内存 void kill() {// Free imagesSDL_FreeSurface( winSurface );// QuitSDL_DestroyWindow( win );SDL_Quit(); }int main(int argc, char* argv[]) {SDL_Init( SDL_INIT_EVERYTHING ); // 初始化SDL2所有部分win = SDL_CreateWindow( "MyWindow", 100, 100, 640, 480, SDL_WINDOW_SHOWN ); // 创建窗口winSurface = SDL_GetWindowSurface( win ); // 基于窗口创建“表面”SDL_FillRect( winSurface, NULL, SDL_MapRGB( winSurface->format, 0, 0, 0 )); // 绘制矩形SDL_UpdateWindowSurface( win ); // 更新窗口// KeyInPut();// WinClose();MouseInput();kill(); // 清理释放return 0; }
-
几何渲染
区别于上述几个案例,在绘制图形时都是使用基于软件或 CPU 渲染。在下述中将会引入到渲染器
SDL_Renderer
结构来进行渲染绘制,它的渲染速度会得到提高。而且不在采用基于SDL_Surface
表面结构的绘制,而是通过操作渲染器SDL_Renderer
结构,定位,绘制,更新,来完成帧图。如何更多的绘制,如点、线、面等可以查看函数文档。代码案例中,实现的效果和案例1基本相同,被注释掉的是,之前基于表面结构实现的方法。
SDL_Window* win; SDL_Renderer* renderer;int main(int argc, char* argv[]) {SDL_Rect rect;rect.x = 20; rect.y = 20; rect.w = 100; rect.h = 100;SDL_Init( SDL_INIT_EVERYTHING ); // 初始化SDL2所有部分// win = SDL_CreateWindow( "MyWindow", 100, 100, 640, 480, SDL_WINDOW_SHOWN ); // 创建窗口SDL_CreateWindowAndRenderer( 640, 480, NULL, &win, &renderer ); // 创建窗口和渲染器// winSurface = SDL_GetWindowSurface( win ); // 基于窗口创建“表面”// SDL_FillRect( winSurface, NULL, SDL_MapRGB( winSurface->format, 0, 0, 0 )); // 绘制矩形SDL_SetRenderDrawColor( renderer, 255, 0, 0, 255 );SDL_RenderFillRect(renderer, &rect);// SDL_UpdateWindowSurface( win ); // 更新窗口SDL_RenderPresent( renderer ); // 更新窗口while (1); // 卡住界面(保持窗口)// 清理释放SDL_DestroyRenderer( renderer );SDL_DestroyWindow( win );return 0; }SDL_Window* win; SDL_Renderer* renderer;int main(int argc, char* argv[]) {SDL_Rect rect;rect.x = 20; rect.y = 20; rect.w = 100; rect.h = 100;SDL_Init( SDL_INIT_EVERYTHING ); // 初始化SDL2所有部分// win = SDL_CreateWindow( "MyWindow", 100, 100, 640, 480, SDL_WINDOW_SHOWN ); // 创建窗口SDL_CreateWindowAndRenderer( 640, 480, NULL, &win, &renderer ); // 创建窗口和渲染器// winSurface = SDL_GetWindowSurface( win ); // 基于窗口创建“表面”// SDL_FillRect( winSurface, NULL, SDL_MapRGB( winSurface->format, 0, 0, 0 )); // 绘制矩形SDL_SetRenderDrawColor( renderer, 255, 0, 0, 255 );SDL_RenderFillRect(renderer, &rect);// SDL_UpdateWindowSurface( win ); // 更新窗口SDL_RenderPresent( renderer ); // 更新窗口while (1); // 卡住界面(保持窗口)// 清理释放SDL_DestroyRenderer( renderer );SDL_DestroyWindow( win );return 0; }
-
创建纹理
因为上述讲了渲染可以通过表面结构或渲染器结构,所以有两种。在接下来的代码案例中,将只会介绍渲染器来进行渲染的结构。关于纹理,“纹理是表面的 GPU 渲染等效物”,应该寓意着纹理的渲染是通过GPU,且更高效的。
下述代码只提供,纹理创建到渲染。像原教程中还举例有,纹理透明度改变,图形翻转以及改变模式之类的暂不列出。
SDL_Rect rect; SDL_Window* win; SDL_Surface* image; SDL_Texture* texture; SDL_Renderer* renderer;int main(int argc, char* argv[]) {SDL_Init( SDL_INIT_EVERYTHING ); // 初始化SDL2所有部分SDL_CreateWindowAndRenderer( 640, 480, NULL, &win, &renderer ); // 创建窗口和渲染器// 获取logo图片,并创建纹理image = SDL_LoadBMP( "D:\\Desktop\\MyData\\CLion\\Demo\\logo.bmp" );texture = SDL_CreateTextureFromSurface( renderer, image );SDL_FreeSurface( image );rect.x = 20; rect.y = 20; rect.w = 300; rect.h = 300;SDL_RenderCopy( renderer, texture, NULL, &rect );SDL_RenderPresent(renderer);// 清理释放SDL_DestroyRenderer( renderer );SDL_DestroyWindow( win );return 0; }
-
声音和扩展库
SDL虽然有着广泛的API,但是部分区域还是要借助扩展库。这里将介绍使用到
SDL_Image
和SDL_Mixer
库。扩展库都需要导入其代码,这里暂时就先不介绍了。// SDL_Image IMG_Init( IMG_INIT_JPG | IMG_INIT_PNG ); // 初始化 SDL_Surface* image = IMG_Load("image.png"); // 导入图片 IMG_Quit(); // 销毁// SDL_Mixer Mix_OpenAudio( 44100, MIX_DEFAULT_FORMAT, 2, 1024 ); // 初始化 /* 加载音频 */ Mix_Music* music; Mix_Chunk* sound; music = Mix_LoadMUS("music.wav"); // 加载音乐 sound = Mix_LoadWAV("sound.mp3"); // 加载声音 /* 播放音乐 */ Mix_PlayMusic( music, -1 ); /* 播放声音 */ Mix_PlayChannel( -1, sound, 0 ); Mix_Pause( channel ); SDL_Delay( 1000 ); Mix_Resume( channel ); /* 销毁 */ Mix_FreeChunk( sound ); Mix_FreeMusic( music ); Mix_Quit();
-
文本渲染和输入
该功能也是基于扩展库,基于
SDL_ttf.h
。SDL_Surface* text; SDL_Texture* text_texture; SDL_Color color = { 0, 0, 0 };/* 初始化 */ TTF_Init(); /* 渲染文本 */ text = TTF_RenderText_Solid( font, "Hello World!", color ); text_texture = SDL_CreateTextureFromSurface( renderer, text ); SDL_Rect dest = { 0, 0, text->w, text->h }; SDL_RenderCopy( renderer, text_texture, &dest ); /* 文本输入 */ SDL_StartTextInput(); string in; bool running = true;while ( running ) {SDL_Event ev;while ( SDL_PollEvent( &ev ) ) {if ( ev.type == SDL_TEXTINPUTEVENT ) {in += ev.text.text;// cout << " > " << in << endl;} else if ( ev.type == SDL_KEYDOWN && ev.key.keysym.sym == SDLK_BACKSPACE && in.size()) {in.pop_back();// cout << " > " << in << endl;} eles if ( ev.type == SDL_QUIT ) {running = false;}} }SDL_StopTextInput(); /* 关闭 */ TTF_CloseFont( font ); TTF_Quit();
-
计时:帧速率、物理、动画
该章节讲到的概念案例都较为易懂,所以直接附上教程中的代码。现在先看个大概,可以在使用到的时候在多看看。
/***** 定时 *****/ Uint32 ticks = SDL_GetTicks(); // ...中途操作 Uint32 end = SDL_GetTicks(); float secondsElapsed = (end - start) / 1000.0f;/***** 更高精度计数器 *****/ Uint64 start = SDL_GetPerformanceCounter(); // ...中途操作 Uint64 end = SDL_GetPerformanceCounter(); float secondsElapsed = (end - start) / (float)SDL_GetPerformanceFrequency();/***** 更高精度计数器 *****/ bool running = true; while (running) {Uint64 start = SDL_GetPerformanceCounter();// 事件循环// 物理循环// 呈现循环Uint64 end = SDL_GetPerformanceCounter();float elapsed = (end - start) / (float)SDL_GetPerformanceFrequency();printf("Current FPS: %s\r\n",to_string(1.0f / elapsed)); }/***** 限制FPS速率 *****/ bool running = true; while (running) {Uint64 start = SDL_GetPerformanceCounter();// 事件循环// 物理循环// 呈现循环Uint64 end = SDL_GetPerformanceCounter();float elapsedMS = (end - start) / (float)SDL_GetPerformanceFrequency() * 1000.0f;// 上限为60FPSSDL_Delay(floor(16.666f - elapsedMS));}/***** 垂直同步 *****/ SDL_Renderer* renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_PRESENTVSYNC | SDL_RENDERER_ACCELERATED );/***** 物理 *****/ bool running; Uint32 lastUpdate = SDL_GetTicks();while (running) {// 事件循环// 物理循环Uint32 current = SDL_GetTicks();// 计算dT(单位:秒)float dT = (current - lastUpdate) / 1000.0f;for ( /* 对象列表 */ ) {object.position += object.velocity * dT;}// 设置更新时间lastUpdate = current;// 呈现循环 }/***** 动画 *****/ float animatedFPS = 24.0f; bool running;while (running) {// 事件循环// 物理循环// 呈现循环Uint32 current = SDL_GetTicks();// 计算dT(单位:秒)for ( /* 对象列表 */ ) {float dT = (current - object.lastUpdate) / 1000.0f;int framesToUpdate = floor(dT / (1.0f / animatedFPS));if (framesToUpdate > 0) {object.lastFrame += framesToUpdate;object.lastFrame %= object.numFrames;object.lastUpdate = current;}render(object.frames[object.lastFrame]);} }
资料
原版教程用到的扩展库,和目前SDL最新稳定版的包
SDL2-devel-2.30.11-mingw.zip * 1
SDL2_image-devel-2.8.4-mingw.zip * 1
SDL2_mixer-devel-2.7.2-mingw.zip * 1
SDL2_ttf-devel-2.22.0-mingw.zip * 1
链接: https://pan.baidu.com/s/1A8wkpUpnAkLihmaocIvxeA 提取码: v6wk