# A Brief Introduction to SDL **Repository Path**: ZerAx/a-brief-introduction-to-sdl ## Basic Information - **Project Name**: A Brief Introduction to SDL - **Description**: No description available - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2020-11-02 - **Last Updated**: 2020-12-19 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # SDL2使用简介 ## 开发环境搭建 ### Windows #### Visual Studio [VS2019+SDL2.0环境准备](https://blog.csdn.net/Ciellee/article/details/108627412) #### Code::Blocks [Code::Blocks如何植入SDL2](https://zhuanlan.zhihu.com/p/141400326) #### Dev-Cpp [Dev-Cpp开发环境配置SDL库](https://blog.csdn.net/konga/article/details/44311741) ### CMake(CLion) ***本条需要预先安装CMake*** 下载[SDL2源代码](https://www.libsdl.org/release/SDL2-2.0.12.zip) 在源代码目录下输入指令 ``` powershell cmake -S . -B build cmake --build build --target install ``` 然后在项目的CMakeLists.txt下的`add_executable(xxx ...)`命令之后加入`target_link_libraries(xxx SDL2::SDL2 SDL2::SDL2main)` ``` cmake add_executable(YourProjectName main.cpp) target_link_libraries(YourProjectName SDL2::SDL2 SDL2::SDL2main) ``` ### 问题总结 Q : undefined reference to "xxxx" ? A : 请统一使用i686(32位)的头文件和库,目前还没有找到用x86_64(64位)头文件和库的方法 ## SDL2库使用介绍 首先包含SDL2头(先要搭建好环境) ``` c #include ``` 或 ``` c #include ``` 然后按格式创建main函数(很关键,不然SDL2找不到) ``` c int main(int argc, char** argv) { return 0; } ``` ### 初始化 SDL初始化需要调用`SDL_Init(unsigned int flag)`函数。该函数接收一个参数用于表示初始化哪些模块,这里直接填入`SDL_INIT_EVERYTHING`这一flag初始化所有模块。 当初始化成功时函数返回0。因此在main函数中,使用如下代码完成初始化。 ``` c int main(int argc, char** argv) { if (SDL_Init(SDL_INIT_EVERYTHING) != 0) { return -1; } return 0; } ``` PS:如果在此处报错,那就是[环境搭建](#开发环境搭建)仍有问题 ### 创建窗口 使用`SDL_CreateWindow(const char* title, int x, int y, int w, int h, unsigned int flag)`来取得一个SDL_Window*(这是表示一个窗口的变量)未来将通过一系列函数操作这个变量。 其中 + title: 窗口名。 + x, y: 窗口位置,用SDL_WINDOWPOS_UNDEFINED来表示不预定位置。 + w, h: 窗口的长(width)宽(height),这里就直接写800, 600了。 + flag: 窗口创建的一些flag,这里直接填入SDL_WINDOW_SHOWN。 在程序执行完毕后,使用`SDL_DestroyWindow(SDL_Window* window)`来销毁一个窗口。 ``` c int main(int argc, char** argv) { SDL_Window* window = NULL; if (SDL_Init(SDL_INIT_EVERYTHING) != 0) { return -1; } window = SDL_CreateWindow("Your Window Title", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 800, 600, SDL_WINDOW_SHOWN); SDL_DestroyWindow(window); return 0; } ``` 这时候编译运行应该会闪一个黑框,可以在销毁窗口前调用`SDL_Delay(2000)`来使窗口持续显示(阻塞线程)2000毫秒。 ``` c int main(int argc, char** argv) { SDL_Window* window = NULL; if (SDL_Init(SDL_INIT_EVERYTHING) != 0) { return -1; } window = SDL_CreateWindow("Your Window Title", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 800, 600, SDL_WINDOW_SHOWN); SDL_Delay(2000); SDL_DestroyWindow(window); return 0; } ``` ### 绘制矩形 > 注:SDL的坐标系是以左上角为原点,往右为x正方向,下为y正方向。 下面的代码将在(350, 250)处绘制出一个边长为100像素的红色的正方形。下面将对代码进行讲解。 ``` c int main(int argc, char** argv) { SDL_Window* window = NULL; SDL_Renderer* renderer = NULL; struct SDL_Rect rect = { 350, 250, 100, 100 }; if (SDL_Init(SDL_INIT_EVERYTHING) != 0) { return -1; } window = SDL_CreateWindow("Your Window Title", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 800, 600, SDL_WINDOW_SHOWN); renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED); SDL_SetRenderDrawColor(renderer, 0xff, 0, 0, 0xff); SDL_RenderFillRect(renderer, &rect); SDL_RenderPresent(renderer); SDL_Delay(2000); SDL_DestroyRenderer(renderer); SDL_DestroyWindow(window); return 0; } ``` ![Fill a rect](fill_a_rect.png) 使用`SDL_CreateRenderer(SDL_Window* window, int index, unsigned int flag)`来对这个窗口创建一个渲染器(SDL_Renderer*)。 + window: 先前创建的window变量 + index: 标识渲染器的序号,这里只用一个所以是-1 + flag: 标识渲染器的渲染方式,这里使用GPU渲染所以用`SDL_RENDERER_ACCELERATED` 类似地,可以使用`SDL_DestroyRenderer(SDL_Renderer* renderer)`来销毁一个渲染器。 可以用渲染器+图形绘制图形,例如填充一个矩形:`SDL_RenderFillRect(SDL_Renderer* renderer, const SDL_Rect* rect)` 不过首先要通过`SDL_SetRenderDrawColor(SDL_Renderer* renderer, unsigned char r, unsigned char g, unsigned char b, unsigned char a)`来设置渲染器颜色。 在画完所有东西以后,调用`SDL_RenderPresent(SDL_Renderer* renderer)`来提交改动。 ### 刷新屏幕 上面的代码都只是一段顺序执行的代码,画的再多也只能画一张静态的图片,而做游戏,肯定是得让画面动起来的。动起来自然就需要反复对画面进行绘制,因此我们通过`while`循环来做到这一点。 而每一帧开始绘制之前,需要清除上一帧所绘制的内容。不然会在屏幕上出现拖影。换句话说,先前画的所有东西都会堆在一起。 调用`SDL_RenderClear(SDL_Renderer* renderer)`来清除屏幕。别忘了在清除前设定好颜色。 ``` c //...省略上面的代码... // 用白色清空屏幕 SDL_SetRenderDrawColor(renderer, 0xff, 0xff, 0xff, 0xff); SDL_RenderClear(renderer); //...省略下面的代码... ``` ---------------------------------------------------------------- #### 读取输入 在这之前,需要介绍SDL读取输入的方式。不然程序就没办法通过正常方式关掉了。 SDL读取输入的方式有两种: 1. 直接扫描当前键盘的状态。 2. 通过系统事件间接读取输入。 ##### 直接扫描键盘 通过调用`SDL_GetKeyboardState(int* len)`来获取一个数组的指针。通过`if`判断键盘按键对应该数组的下标的值。举例,如果要判断W键是否被按下: ``` c const unsigned char* keyMap = SDL_GetKeyboardState(NULL); if (keyMap[SDL_SCANCODE_W]) { printf("W键被按下!!"); } ``` ##### 读取系统事件 函数`SDL_PollEvent(SDL_Event* event)`在有事件时会返回1,并将事件写入`event`,否则返回0。因此我们可以用以下方式来读取系统事件。 ``` c SDL_Event event; while (SDL_PollEvent(&event)) { // 处理事件... } ``` ---------------------------------------------------------------- #### 实例 下面的代码将让上一节中的矩形按WASD键动起来,而且可以单击窗口上的“叉”来关闭这个窗口。 ``` c int main(int argc, char** argv) { SDL_Window* window = NULL; SDL_Renderer* renderer = NULL; // 创建矩形 struct SDL_Rect rect = { 350, 250, 100, 100 }; int running = 1; if (SDL_Init(SDL_INIT_EVERYTHING) != 0) { return -1; } window = SDL_CreateWindow("Your Window Title", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 800, 600, SDL_WINDOW_SHOWN); renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED); while (running) { SDL_Event event; while (SDL_PollEvent(&event)) { if (event.type == SDL_QUIT) { running = 0; } } // 扫描键盘 const unsigned char* keyMap = SDL_GetKeyboardState(NULL); // 如果W或S按下,更新矩形的上下位置 if (keyMap[SDL_SCANCODE_W]) { rect.y -= 1; } else if (keyMap[SDL_SCANCODE_S]) { rect.y += 1; } // 如果A或D按下,更新矩形的左右位置 if (keyMap[SDL_SCANCODE_A]) { rect.x -= 1; } else if (keyMap[SDL_SCANCODE_D]) { rect.x += 1; } // 清除屏幕 SDL_SetRenderDrawColor(renderer, 0xff, 0xff, 0xff, 0xff); SDL_RenderClear(renderer); // 绘制矩形 SDL_SetRenderDrawColor(renderer, 0xff, 0, 0, 0xff); SDL_RenderFillRect(renderer, &rect); // 更新内容 SDL_RenderPresent(renderer); } SDL_DestroyWindow(window); return 0; } ``` ![RectMoving1](rect_movement_1.png) ![RectMoving2](rect_movement_2.png) ### 绘制图片 绘制图片需要先读取再使用`SDL_RenderCopy()`将其作为材质附加到SDL_Renderer上。以下代码展示了如何简单读取图片并渲染出来。 ```c #include #include int main(int argc, char *argv[]) { const int SCREEN_WIDTH = 800; const int SCREEN_HEIGHT = 600; SDL_Window *window = NULL; SDL_Renderer *renderer = NULL; if (SDL_Init(SDL_INIT_EVERYTHING) != 0) { printf("SDL could not initialize! SDL error: %s\n", SDL_GetError()); return -1; } window = SDL_CreateWindow("SDL Guide", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, SCREEN_WIDTH, SCREEN_HEIGHT, SDL_WINDOW_SHOWN); //读取图片,SDL_LoadBMP中的字符串是图片路径 SDL_Surface* surface = SDL_LoadBMP("dummy.bmp"); renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED); //创建一个Rect设定图片渲染时的位置和大小,这里直接用上面的矩形 const struct SDL_Rect rect = {350, 250, 100, 100}; //把SDL_Surface转换为SDL_Texture,即材质 SDL_Texture* tex = SDL_CreateTextureFromSurface(renderer,surface); //设定背景颜色 SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255); SDL_RenderClear(renderer); //附加材质 SDL_RenderCopy(renderer, tex, NULL, &rect); SDL_RenderPresent(renderer); SDL_Delay(2000); SDL_DestroyRenderer(renderer); SDL_Quit(); return 0; } ``` 效果图 ![load_image](load_image.jpg) `SDL_Surface`接收`SDL_LoadBMP`读取的图片,再使用`SDL_CreateTextureFromSurface`转换为材质。 [图片素材](https://gitee.com/ZerAx/a-brief-introduction-to-sdl/raw/master/dummy.bmp)