(译)SDL编程入门(1)Hello SDL
Hello SDL 你的第一个图形窗口
你学会了C++的基础知识,但你厌倦了
制作基于文本的小程序。为了使用诸如图形、声音、键盘、操纵杆等东西,你需要一个API(应用程序员接口),将所有这些硬件功能转化为C++可以交互的东西。
这就是SDL所做的事情。它将Windows/Linux/Mac/Android/iOS等工具包装起来,让你可以用SDL编写代码,然后编译到它所支持的任何平台。为了使用它,你需要安装它。
SDL作为一个动态链接库。一个动态链接库有3个部分。
- 头文件(Library.h)
- 库文件 (Library.lib for windows or libLibrary.a for *nix)
- 二进制文件(Windows的Library.dll或*nix的Library.so)
您的编译器在编译时需要能够找到头文件,以便它知道SDL_Init()和其他SDL函数和结构是什么。您可以配置您的编译器在SDL头文件所在的额外目录中搜索,或者将头文件与编译器自带的其他头文件放在一起。如果编译器抱怨说找不到SDL.h,那就意味着头文件不在编译器寻找头文件的地方。
编译器编译完你所有的源文件后,它必须将它们链接在一起。为了让程序正确链接,它需要知道所有函数的地址,包括SDL的函数。对于动态链接的库,这些地址在库文件中。库文件中有导入地址表,因此您的程序可以在运行时导入函数。和头文件一样,你可以配置你的编译器在SDL库文件所在的额外目录中搜索,或者把库文件和编译器自带的其他库文件放在一起。你还必须告诉链接器,要针对链接器中的库文件进行链接。如果链接器抱怨找不到 -lSDL 或 SDL2.lib,这意味着库文件不在链接器寻找库文件的地方。如果链接器抱怨说有未定义的引用,可能意味着它从未被告知要链接库。
当你的程序被编译和链接后,你需要在运行它时能够针对库进行链接。为了运行一个动态链接的应用程序,你需要能够在运行时导入库的二进制文件。当你运行程序时,你的操作系统需要能够找到库二进制文件。你可以把库二进制文件和你的可执行文件放在同一个目录下,或者放在你的操作系统保存库二进制文件的目录下。
译者注:打开 SDL官网[1],下载Windows下的DLL动态库
建议使用MinGW搭建Windows上的C语言开发环境,详情可阅读译者博客《程序员C语言快速上手——环境准备篇(一)》
设置好SDL后,我们将介绍如何创建SDL2窗口。
本教程涵盖了第一个重要的步骤:让一个窗口弹出。
现在你已经设置好了SDL,是时候制作一个赤裸裸的SDL图形应用程序,在屏幕上渲染一个四边形。
//Using SDL and standard IO
#include <SDL.h>
#include <stdio.h>
// 屏幕尺寸常数`在这里插入代码片`
const int SCREEN_WIDTH = 640;
const int SCREEN_HEIGHT = 480;
在我们的源文件的顶部,我们包含SDL,因为我们需要SDL函数和数据类型来制作任何SDL代码。我们还将包含C标准IO来打印错误到控制台。你可能更习惯于使用iostream,但我在我的应用程序中使用printf,因为它更安全。对于这些早期的应用,使用你最习惯的东西。
在加入头文件后,我们声明我们要渲染的窗口的宽度和高度。
int main( int argc, char* args[] ){
// 我们要渲染的窗口
SDL_Window* window = NULL;
// 窗口所包含的表面
SDL_Surface* screenSurface = NULL;
// 初始化SDL
if( SDL_Init( SDL_INIT_VIDEO ) < 0 ){
printf( "SDL could not initialize! SDL_Error: %sn", SDL_GetError() );
}else{
// 创建窗口
window = SDL_CreateWindow( "SDL Tutorial", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, SCREEN_WIDTH, SCREEN_HEIGHT, SDL_WINDOW_SHOWN );
if( window == NULL ){
printf( "Window could not be created! SDL_Error: %sn", SDL_GetError() );
}else{
// 获取窗口表面
screenSurface = SDL_GetWindowSurface( window );
// 用白色填充表面
SDL_FillRect( screenSurface, NULL, SDL_MapRGB( screenSurface->format, 0xFF, 0xFF, 0xFF ) );
// 更新表面
SDL_UpdateWindowSurface( window );
// 等待2秒
SDL_Delay( 2000 );
}
}
// 销毁窗户
SDL_DestroyWindow( window );
// 退出SDL子系统
SDL_Quit();
return 0;
}
这是我们主函数的顶部。很重要的一点是,函数的参数是一个整数,后面是一个char*数组,返回类型是一个整数。任何其他类型的main函数都会导致对main的未定义引用。SDL需要这种类型的main,所以它能兼容多种平台。
然后我们声明我们的SDL窗口,我们将在稍后创建这个窗口。继而我们有一个屏幕SDL表面。SDL表面只是一个2D图像。二维图像可以从文件中加载,也可以是窗口中的图像。在这种情况下,它将是我们在屏幕上看到的窗口内部的图像。
在声明我们的窗口和屏幕表面后,我们初始化SDL。在没有初始化SDL之前,你不能调用任何SDL函数。由于我们关心的只是使用SDL的视频子系统,所以我们只将SDL_INIT_VIDEO标志传递给它。
当出现错误时,SDL_Init返回-1。当出现错误时,我们要把发生的事情打印到控制台,否则应用程序只会闪烁一秒钟,然后就会消失。
如果你从来没有使用过printf,它代表的是打印格式。它将第一个参数中的字符串与下面参数中的变量一起打印出来。当这里出现错误时,"SDL could not initialize! SDL_Error: "将被写入控制台,后面是SDL_GetError返回的字符串。那个%s是特殊的格式。%s的意思是从我们的变量列表中输出一个字符串。由于SDL_GetError是唯一的参数,所以它返回的字符串将被加上。SDL_GetError 返回一个 SDL 函数产生的最新错误。
SDL_GetError是一个非常有用的函数。每当出现错误时,你需要知道原因。SDL_GetError 会让你知道任何 SDL 函数内部是否发生了错误。
如果SDL初始化成功,我们要使用SDL_CreateWindow创建一个窗口。第一个参数设置窗口的标题或窗口的这一部分:
接下来的两个参数定义了窗口创建的x和y位置。由于我们并不关心它创建的位置,所以我们只需将x和y的位置放入SDL_WINDOWPOS_UNDEFINED。
接下来的两个参数定义了窗口的宽度和高度。最后一个参数是创建标志。SDL_WINDOW_SHOWN确保窗口在创建时被显示。
如果出现错误,SDL_CreateWindow会返回NULL。如果没有窗口,我们要把错误打印出来到控制台。
如果窗口创建成功,我们希望获得窗口的表面,以便我们可以对其进行绘制。SDL_GetWindowSurface就可以做到这一点。
为了保持本教程的简单性,我们在这里要做的就是使用SDL_FillRect将窗口的表面填充为白色。在这里不要太担心这个函数。本教程只关心如何让一个窗口弹出。
关于渲染的一个重要的事情是,仅仅因为你在屏幕表面画了一些东西,并不意味着你会看到它。当你完成了所有的绘制后,你需要更新窗口,使其显示出你绘制的所有内容。调用SDL_UpdateWindowSurface就可以做到这一点。
如果我们所做的只是创建窗口,填充它,然后更新它,我们将看到的只是一个窗口闪动一秒钟,然后关闭。为了不让它消失,我们将调用SDL_Delay。SDL_Delay会等待一个给定的毫秒量。毫秒是1/1000秒。这意味着上面的代码将使窗口等待2000个1/1000秒或2秒。
需要注意的是,当SDL延迟时,它不能接受键盘或鼠标的输入。当你运行这个程序时,它没有反应,不要惊慌。我们还没有给它处理鼠标和键盘的代码。
当窗口在那里延迟2秒钟后,我们将销毁该窗口以释放其内存。这也将处理我们从中获得的SDL_Surface。释放所有内容后,我们退出SDL并返回0以终止程序。
参考资料
[1]
SDL官网: https://www.libsdl.org/download-2.0.php
[2]
原文链接: http://www.lazyfoo.net/tutorials/SDL/01_hello_SDL/index2.php
- Scala语法基础之隐式转换
- SparkSql的优化器-Catalyst
- Scala语言基础之结合demo和spark讲实现链式计算
- Spark高级操作之json复杂和嵌套数据结构的操作二
- Spark高级操作之json复杂和嵌套数据结构的操作一
- hadoop系列之基础系列
- Spark的调度系统
- Spark Structured Streaming的高效处理-RunOnceTrigger
- Spark度量系统相关讲解
- Spark Structured Streaming高级特性
- Table API&SQL的基本概念及使用介绍
- 使用Linq to Sql 创建数据库和表
- Flink DataSet编程指南-demo演示及注意事项
- 解决 wcf HTTP 无法注册 另一应用程序正在使用 TCP 端口 80
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- jQuery 事件注册和事件处理
- 10.5 块级盒子模型(原盒子模型):两种盒子有什么不同?
- Android Material UI控件之MaterialButton
- jQuery (事件、拷贝)对象
- 10.6 border-color简写属性:如何理解四值语法?
- jQuery 插件
- dotnet 基于 dotnet format 的 GitHub Action 自动代码格式化机器人
- 10.7 border-width边框粗细:outline与border有什么不同?
- WPF 非客户区的触摸和鼠标点击响应
- Flink 解决 No ExecutorFactory found to execute the application
- 10.8 如何用js验证一下boz-sizing样式对块级盒子大小的影响?
- 10.9 块级盒子的内外边距:如何使用box-sizing重新定义盒子模式?
- 10.10 圆角边框border-radius与盒子阴影:如何使用它实现圆等特殊形状?
- 函数内部的this指向
- 正则表达式在js中的使用