文章

SDL图形库及其相关套件库的基本使用

在游戏开发的语境下,搭配以示例代码,简单介绍了SDL库以及相关的渲染、字体、混音等库的基本使用

SDL图形库及其相关套件库的基本使用

一、基本信息

1.1 关于SDL

官方API文档参见此处

  • SDL(Simple DirectMedia Layer简单多媒体库)是一个C语言图形库,相比EasyX使用Windows的GDI进行封装从而只能在Windows平台运行,SDL可以跨平台运行
  • SDL广受行业验证
    • Godot引擎提供SDL可选项
    • Pygame库就是对SDL的Python封装
    • 游戏”消逝的光芒“基于SDL制作

1.2 常用的库

二、初始化

2.1 准备操作

2.1.1 宏定义

  • 由于SDL库中的SDL_main.h文件中定义了一个名为main的宏,所以这会这与原本的main函数产生冲突,可以通过以下两种方法之一解决该问题
  • 第一种:在包含SDL.hSDL_main.h之前定义SDL_MAIN_HANDLED
1
2
#define SDL_MAIN_HANDLED
#include "SDL.h"
  • 第二种:通过在main函数前取消main的定义来防止报错
1
2
#include "SDL.h"
#undef main

2.1.2 头文件引入

  • 应当先在VS解决方案下的主项目的属性配置中添加引入库的头文件目录、静态库路径与名称、如果有动态库则拖入项目目录内,详细操作参考专门的笔记(若使用VSCode也请参考相关笔记)
1
2
3
4
5
6
7
8
9
10
11
12
//在引入SDL.h前将此宏定义,否则main函数在预处理阶段会被define为SDL_main
#define SDL_MAIN_HANDLED

#include <SDL.h>
//图像支持
#include <SDL_image.h>
//混音支持
#include <SDL_mixer.h>
//字体支持
#include <SDL_ttf.h>
//图形支持(此处仅引用了部分支持)
#include <SDL2_gfxPrimitives.h>

2.2 主函数内的初始化

2.2.1 SDL库

1
2
//初始化SDL库的所有子系统
SDL_Init(SDL_INIT_EVERYTHING);

2.2.2 SDL_image库

1
2
//初始化SDL_image库的各支持格式
IMG_Init(IMG_INIT_JPG | IMG_INIT_PNG);

2.2.3 SDL_ttf库

1
2
//初始化SDL_ttf库
TTF_Init();

2.2.4 SDL_mixer库

1
2
//初始化SDL_mixer库的支持格式
Mix_Init(MIX_INIT_MP3);
  • 此库还需要执行Mix_OpenAudio函数打开混音器的声道
    • 第一个参数为音频采样率,单位为赫兹,此处44100(CD质量)为常用的参数
    • 第二个参数为解码的音频格式,此处使用的为比较通用的格式处理方案
    • 第三个参数为声道数,立体声就是2个声道,单声道就填1即可
    • 第四个参数为音频解码缓冲区大小,较小的缓冲区可以减小播放延迟,但会增加处理负担即性能占用大,一般使用比较折中的2048字节
1
2
//Mix_OpenAudio(int frequency, Uint16 format, int channels, int chunksize)
Mix_OpenAudio(44100, MIX_DEFAULT_FORMAT, 2, 2048);
  • 上面这行配置在大多数游戏中是固定的

三、窗口创建

3.1 SDL_CreateWindow创建窗口

3.1.1 函数说明

1
SDL_CreateWindow(const char *title, int x, int y, int w, int h, Uint32 flags);
  • title:窗口标题(接收UTF-8编码格式字符,记得给字符串前加上u8
  • x:窗口的位置横坐标
  • y:窗口的位置纵坐标,xy都有预定义的宏
  • w:窗口的宽度
  • h:窗口的高度
  • flags:窗口的标志(样式),有预定义的宏
    • SDL_WINDOW_SHOWN:没啥特殊要求可以用这个来仅仅使窗口显示
    • SDL_WINDOW_FULLSCREEN:改变系统分辨率的全屏,一般是老游戏在用
    • SDL_WINDOW_FULLSCREEN_DESKTOP:跟随系统分辨率的全屏,兼容性更好
    • SDL_WINDOW_OPENGL:使用OpenGL
    • SDL_WINDOW_VULKAN:使用Vulkan
    • SDL_WINDOW_HIDDEN:创建了但是隐藏窗口
    • SDL_WINDOW_BORDERLESS:无边框窗口
    • SDL_WINDOW_MINIMIZED:窗口最小化
    • SDL_WINDOW_MAXIMIZED:窗口最大化

3.1.2 函数使用

  • 一个创建窗口的示例如下
1
2
//从屏幕中心显示一个标题为"WhythZ"的1280x720的一般样式的窗口 
SDL_Window* window = SDL_CreateWindow(u8"WhythZ", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 1280, 720, SDL_WINDOW_SHOWN);

3.2 SDL_CreateRenderer加载渲染器

3.2.1 函数说明

1
SDL_CreateRenderer(SDL_Window * window, int index, Uint32 flags);
  • window:渲染器应用的窗口
  • index:渲染的一个初始化索引,一般使用比较通用的-1
  • flags:怎样渲染,具体的类型细节可以查看头文件中的注释

3.2.2 函数使用

  • 渲染器加载的一个示例如下,窗口使用的是我们上面SDL_CreateWindow创建的窗口
1
2
//加载渲染器(使用GPU进行加速)到刚刚创建的窗口上
SDL_Renderer* renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);

四、图片渲染

4.1 加载图片

图片的加载在主循环之前完成

4.1.1 从硬盘到内存

  • 把要渲染的图片(图片必须存在,不然会报错空索引)从硬盘加载到内存中(SDL_Surface是一个内存结构体)
1
SDL_Surface* imgSurface = IMG_Load("Assets/wz.jpg");
  • 图片以路径被搜索,此处被加载的图片是解决方案下所属项目的目录中的Assets文件夹内的名为wz.jpg的一个图片文件

4.1.2 从内存到GPU

  • 将图片转化为GPU数据(纹理数据),然后使得渲染器可以通过GPU将图片渲染出来
1
SDL_Texture* imgTexture = SDL_CreateTextureFromSurface(renderer, imgSurface);

4.2 渲染图片

图片的渲染在游戏主循环内执行

4.2.1 设定图片或者图形的渲染颜色

  • SDL_SetRenderDrawColor函数确定渲染的颜色,除了渲染器外另接收RGB三色与Alpha(记录图像的透明度信息的256级灰度,最大的255表示完全不透明,最小的0表示完全透明),
1
2
//此处设定渲染的颜色为纯黑不透明
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);

4.2.2 确定图片以什么比例渲染到什么位置

4.2.2.1 函数介绍
  • 注意:SDL_Rect*用于切割SDL_Texture*,空指针nullptr表示无需切割
1
2
3
4
SDL_RenderCopy(SDL_Renderer* renderer,
		SDL_Texture* texture,
		const SDL_Rect* src_rect,
		const SDL_Rect* dst_rect);
  • 第一个参数接收我们配置的渲染器,一般渲染相关的函数第一个参数都接收渲染器
  • 第二个SDL_Texture*参数传入加载图片的纹理数据
  • 第三个参数位接收一个矩形结构体指针SDL_Rect*,用于从第二参数位上加载的SDL_Texture*类型的纹理图片中切割截取指定的部分
  • 被渲染到第四个参数指代的位置上的纹理,是第二三参数得出的切割纹理
    • 第四个参数是SDL_Rect*类型指针,其负责切割的SDL_Texture*纹理对象就是第一参数位传入的renderer正在渲染的目标区域纹理,注意这个目标区域纹理不是我们的屏幕,而是先前通过函数SDL_SetRenderTarget(SDL_Renderer* renderer, SDL_Texture* target)设置为目标对象的那一块纹理图片区域target
    • 如果target就是我们渲染的目标窗口,那么通过上述流程就可以直接将源纹理渲染在目标窗口上了;如果target只是我们要渲染在窗口上的某一块区域,那么目标窗口还需要通过某个单独的SDL_Rect*被切割出一块区域,该区域内即可(用SDL_RenderCopy函数)塞入被切割后的纹理图片target,这个过程可以无限套娃
    • 这意味着第二三参数得出的纹理图片,可能会受第四参数的形状影响,从而被拉伸变形,比如第四参数传入nullptr则意味不着对渲染目标窗口进行切割,则渲染器会将图片拉伸到整个目标窗口上,这样大概率会导致图片变形
4.2.2.2 函数使用示例
  • 在使用函数之前,我们需要在主循环执行之前获取被加载图片的长宽比例,并用一个变量来存储以便在主循环图片渲染过程中使用,来确保以正确比例渲染图片
1
2
3
4
//存储被渲染图片的长宽比例,用于赋予图片被渲染到的矩形上,来确保图片的渲染不会变形
SDL_Rect imgRectangle;
imgRectangle.w = imgSurface->w;
imgRectangle.h = imgSurface->h;
  • 示例代码如下,第四参数位我们传入的是一个与imgTexture长宽比例一致的矩形
1
2
//将图片不经截取地加载到第四参数位的矩形结构体上
SDL_RenderCopy(renderer, imgTexture, nullptr, &imgRectangle);

4.2.3 将图片渲染到窗口上

  • 调用SDL_RenderPresent函数将渲染的内容更新到窗口缓冲区上(函数接收一个渲染器)
1
SDL_RenderPresent(renderer);

4.2.4 在渲染前清屏

  • 在此之前的图片渲染代码仅仅是在每次循环把图片渲染到窗口上,你会发现每次循环所渲染的图片并没有被清除,导致屏幕上堆满图片,故如果我们不想要这样的效果,那么就需需在函数SDL_RenderCopySDL_RenderPresent被调用之前清理屏幕
1
2
3
4
//此处设定渲染的颜色为纯黑不透明
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
//使用设定的不透明黑色填充整个窗口达到清屏的效果
SDL_RenderClear(renderer);
  • SDL_RenderClear函数清除渲染目标会将整个渲染目标即窗口用当前渲染颜色填充,所以这两个语句的顺序是不可以变的,意在用不透明的黑色填充整个窗口以达到清屏的效果,不然如果反过来的话可能会出现被别的不可控的颜色填充全屏的错误

4.3 渲染示例

  • 我们通过以下代码在窗口渲染一个跟随鼠标指针移动的图片
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
#pragma region Preprocessing
//将此宏定义,否则main函数在预处理阶段会被define为SDL_main
#define SDL_MAIN_HANDLED

#include <SDL.h>
//图像支持
#include <SDL_image.h>
#pragma endregion

int main()
{
	#pragma region Initialize
	//初始化SDL库的所有子系统
	SDL_Init(SDL_INIT_EVERYTHING);
	//初始化SDL_image库的各支持格式
	IMG_Init(IMG_INIT_JPG | IMG_INIT_PNG);
	#pragma endregion
	
	#pragma region Window&Render
	//从屏幕中心显示一个标题为"WhythZ"的1280x720的一般样式的窗口 
	SDL_Window* window = SDL_CreateWindow(u8"WhythZ", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 1280, 720, SDL_WINDOW_SHOWN);
	//加载渲染器(使用GPU进行加速)到刚刚创建的窗口上
	SDL_Renderer* renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
	#pragma endregion

	//存储鼠标指针位置
	SDL_Point cursorPos = { 0,0 };

	/*加载要渲染的图片*/
	//把要渲染的图片从硬盘加载到内存中(SDL_Surface是一个内存结构体)
	SDL_Surface* imgSurface = IMG_Load("Assets/wz.jpg");
	//将图片转化为GPU数据(纹理数据),然后使得渲染器可以通过GPU将图片渲染出来
	SDL_Texture* imgTexture = SDL_CreateTextureFromSurface(renderer, imgSurface);
	//存储被渲染图片的长宽比例,用于赋予图片被渲染到的矩形上,来确保图片的渲染不会变形
	SDL_Rect imgRectangle;
	imgRectangle.w = imgSurface->w;
	imgRectangle.h = imgSurface->h;

	//决定游戏主循环是否结束
	bool isQuit = false;
	//用于调用SDL事件,根据不同的事件类型做出相应的反应
	SDL_Event event;

	//游戏主循环
	while (!isQuit)
	{
		/*处理事件以保证窗口正常交互*/
		while (SDL_PollEvent(&event))
		{
			//点击窗口的退出键时触发的SDL_QUIT事件
			if (event.type == SDL_QUIT)
				isQuit = true;
			//鼠标指针移动事件
			if (event.type == SDL_MOUSEMOTION)
			{
				//获取鼠标指针坐标
				cursorPos.x = event.motion.x;
				cursorPos.y = event.motion.y;
			}
		}

		/*处理数据*/
		//获取鼠标指针坐标,并赋值给图片被加载到的矩形上,来确保加载的图片跟随鼠标指针移动
		imgRectangle.x = cursorPos.x;
		imgRectangle.y = cursorPos.y;

		/*渲染绘图*/
		//确定渲染的颜色为纯黑(不透明),接收RGB三色与Alpha(记录图像的透明度信息的256级灰度)
		SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
		//使用设定的不透明黑色填充整个窗口达到清屏的效果
		SDL_RenderClear(renderer);
		//将图片不经截取地加载到第四参数位的矩形结构体上
		SDL_RenderCopy(renderer, imgTexture, nullptr, &imgRectangle);
		//将渲染的内容更新到窗口缓冲区上
		SDL_RenderPresent(renderer);
	}
}

五、文本渲染

5.1 加载字体文件

  • 从特定目录下加载字体文件,第二参数位为int ptsize,用于设置被打开字体的point size大小
1
2
//以32pt的大小加载一个字体对象
TTF_Font* font = TTF_OpenFont("Assets/VonwaonBitmap-16pxLite.ttf", 32);
  • 以图片的方式加载渲染特定字体的文本即可
1
2
3
4
5
6
//存储自定义颜色,此处为纯白色不透明(前三各参数即RGB均为255表示纯白),Alpha值为255不透明
SDL_Color color = { 255,255,255,255 };
//以图片的形式展示文本,传入字体、文本内容、文本颜色
SDL_Surface* textSurface = TTF_RenderUTF8_Blended(font, u8"Girls Band Cry", color);
//转换文本图片为纹理
SDL_Texture* textTexture = SDL_CreateTextureFromSurface(renderer, textSurface);

5.2 渲染指定字体的文本

  • 既然是以渲染图片的方式渲染文本,那么具体操作参考图片的渲染即可

5.3 渲染示例

  • 我们通过以下代码在窗口渲染一段跟随鼠标指针移动的文本
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
#pragma region Preprocessing
//将此宏定义,否则main函数在预处理阶段会被define为SDL_main
#define SDL_MAIN_HANDLED

#include <SDL.h>
//图像支持
#include <SDL_image.h>
//字体支持
#include <SDL_ttf.h>
#pragma endregion

int main()
{
	#pragma region Initialize
	//初始化SDL库的所有子系统
	SDL_Init(SDL_INIT_EVERYTHING);
	//初始化SDL_image库的各支持格式
	IMG_Init(IMG_INIT_JPG | IMG_INIT_PNG);
	//初始化SDL_ttf库
	TTF_Init();
	#pragma endregion
	
	#pragma region Window&Render
	//从屏幕中心显示一个标题为"WhythZ"的1280x720的一般样式的窗口 
	SDL_Window* window = SDL_CreateWindow(u8"WhythZ", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 1280, 720, SDL_WINDOW_SHOWN);
	//加载渲染器(使用GPU进行加速)到刚刚创建的窗口上
	SDL_Renderer* renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
	#pragma endregion

	//存储鼠标指针位置
	SDL_Point cursorPos = { 0,0 };

	/*加载字体*/
	//以32pt的大小加载一个字体对象
	TTF_Font* font = TTF_OpenFont("Assets/VonwaonBitmap-16pxLite.ttf", 32);
	//存储自定义颜色,此处为纯白色不透明(前三各参数即RGB均为255表示纯白),Alpha值为255不透明
	SDL_Color color = { 255,255,255,255 };
	//以图片的形式展示文本,传入字体、文本内容、文本颜色
	SDL_Surface* textSurface = TTF_RenderUTF8_Blended(font, u8"Girls Band Cry", color);
	//转换文本图片为纹理
	SDL_Texture* textTexture = SDL_CreateTextureFromSurface(renderer, textSurface);
	//存储被渲染的文本图片的长宽比例,用于赋予图片被渲染到的矩形上,来确保图片的渲染不会变形
	SDL_Rect textRectangle;
	textRectangle.w = textSurface->w;
	textRectangle.h = textSurface->h;

	//决定游戏主循环是否结束
	bool isQuit = false;
	//用于调用SDL事件,根据不同的事件类型做出相应的反应
	SDL_Event event;

	//游戏主循环
	while (!isQuit)
	{
		/*处理事件以保证窗口正常交互*/
		while (SDL_PollEvent(&event))
		{
			//点击窗口的退出键时触发的SDL_QUIT事件
			if (event.type == SDL_QUIT)
				isQuit = true;
			//鼠标指针移动事件
			if (event.type == SDL_MOUSEMOTION)
			{
				//获取鼠标指针坐标
				cursorPos.x = event.motion.x;
				cursorPos.y = event.motion.y;
			}
		}

		/*处理数据*/
		//获取鼠标指针坐标,并赋值给图片被加载到的矩形上,来确保加载的图片跟随鼠标指针移动
		textRectangle.x = cursorPos.x;
		textRectangle.y = cursorPos.y;

		/*渲染绘图*/
		//确定渲染的颜色为纯黑(不透明),接收RGB三色与Alpha(记录图像的透明度信息的256级灰度)
		SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
		//使用设定的不透明黑色填充整个窗口达到清屏的效果
		SDL_RenderClear(renderer);
		//将图片不经截取地加载到第四参数位的矩形结构体上
		SDL_RenderCopy(renderer, textTexture, nullptr, &textRectangle);
		//将渲染的内容更新到窗口缓冲区上
		SDL_RenderPresent(renderer);
	}
}

六、音频播放

6.1 载入音频文件

在主循环外部进行

  • 从指定路径加载音频文件
1
Mix_Music* music = Mix_LoadMUS("Assets/Hotel California.flac");

6.2 淡入方式播放音频

在主循环外部就可以播放,当然你也可以在主循环内添加一些影响因素

  • Mix_FadeInMusic函数使得音频文件以淡入的方式播放,第二个参数位表示播放的循环次数,-1表示无限循环,第三个参数位表示淡入效果的持续时长,单位为毫秒
1
Mix_FadeInMusic(music, -1, 1500);

七、图形绘制

  • 函数filledCircleRGBA可以绘制圆,第四参数位为半径,再往后为RGB和Alpha
1
2
//在鼠标位置画红色的圆
filledCircleRGBA(renderer, cursorPos.x, cursorPos.y, 55, 255, 0, 0, 200);
  • 函数SDL_RenderPresent同样需要被调用来把绘制出的图形渲染出来
1
2
//将渲染的内容更新到窗口缓冲区上
SDL_RenderPresent(renderer);
  • 下面的代码实现了在鼠标指针位置显示一个跟随鼠标指针移动红色的圆形,特别要注意其中的SDL_RenderClear(renderer);如果在SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);之前调用的话,全屏都会被染成红色,因为SDL_RenderClear函数清除渲染目标会将整个渲染目标即窗口用当前渲染颜色填充,所以这两个的顺序是不可以变的,意在用不透明的黑色填充整个窗口以达到清屏的效果
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
#pragma region Preprocessing
//将此宏定义,否则main函数在预处理阶段会被define为SDL_main
#define SDL_MAIN_HANDLED

#include <SDL.h>
//图像支持
#include <SDL_ttf.h>
//图形支持
#include <SDL2_gfxPrimitives.h>
#pragma endregion

int main()
{
	#pragma region Initialize
	//初始化SDL库的所有子系统
	SDL_Init(SDL_INIT_EVERYTHING);
	//初始化SDL_image库的各支持格式
	IMG_Init(IMG_INIT_JPG | IMG_INIT_PNG);
	#pragma endregion
	
	#pragma region Window&Render
	//从屏幕中心显示一个标题为"WhythZ"的1280x720的一般样式的窗口 
	SDL_Window* window = SDL_CreateWindow(u8"WhythZ", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 1280, 720, SDL_WINDOW_SHOWN);
	//加载渲染器(使用GPU进行加速)到刚刚创建的窗口上
	SDL_Renderer* renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
	#pragma endregion

	//存储鼠标指针位置
	SDL_Point cursorPos = { 0,0 };

	//决定游戏主循环是否结束
	bool isQuit = false;
	//用于调用SDL事件,根据不同的事件类型做出相应的反应
	SDL_Event event;

	//游戏主循环
	while (!isQuit)
	{
		/*处理事件以保证窗口正常交互*/
		while (SDL_PollEvent(&event))
		{
			//点击窗口的退出键时触发的SDL_QUIT事件
			if (event.type == SDL_QUIT)
				isQuit = true;
			//鼠标指针移动事件
			if (event.type == SDL_MOUSEMOTION)
			{
				//获取鼠标指针坐标
				cursorPos.x = event.motion.x;
				cursorPos.y = event.motion.y;
			}
		}

		/*渲染绘图*/
		//确定渲染的颜色为纯黑(不透明),接收RGB三色与Alpha(记录图像的透明度信息的256级灰度)
		SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
		//使用设定的不透明黑色填充整个窗口达到清屏的效果
		SDL_RenderClear(renderer);
		//在鼠标指针处绘制一个红色的圆,第四参数位为半径,再往后为RGB和Alpha
		filledCircleRGBA(renderer, cursorPos.x, cursorPos.y, 55, 255, 0, 0, 200);
		//将渲染的内容更新到窗口缓冲区上
		SDL_RenderPresent(renderer);
	}
}

八、控制帧率

游戏主循环的while循环执行得非常快,导致性能占用过高,游戏也不需要那么高的帧率,所以我们可以人为将其控制在固定帧率,比如60帧;为了更精确地控制帧率,我们采用动态延时的方法

  • 在主循环之前,我们记录以下变量
1
2
3
4
5
6
//需要维持的游戏帧率(frame per second),代表每秒刷新的帧数
int fps = 60;
//此函数获取一个高性能(精度较高)计时器,函数返回的值(计时器跳的总数)作为计时器的起点,通过作差后除以频率才有意义
Uint64 lastCounter = SDL_GetPerformanceCounter();
//频率即每一秒此计时器会跳多少次
Uint64 counterFreq = SDL_GetPerformanceFrequency();
  • 在主循环之中,我们使用以下代码进行动态延时
1
2
3
4
5
6
7
8
9
10
11
12
13
//游戏主循环
while (!isQuit)
{
	//获取当前的计时器跳的总数,用以与主循环前得到的计时器总数作差
	Uint64 currentCounter = SDL_GetPerformanceCounter();
	//作差后转换为双精度浮点,除以频率得到每次循环的时间间隔(单位为秒)
	double delta = (double)(currentCounter - lastCounter) / counterFreq;
	//将当前的次数作为起点,进行下一次循环
	lastCounter = currentCounter;
	//若是帧率超过了限定值,那么就将多余的时间延迟掉防止主循环频率过快;乘以1000是将秒转化为毫秒,因为秒这个单位太大而精度不高
	if (delta * 1000 < 1000.0 / fps)
		SDL_Delay((Uint32)(1000.0 / fps - delta * 1000));
}
本文由作者按照 CC BY-NC-SA 4.0 进行授权