SFML 是一个使用 C++ 编写的多媒体库,包括系统、窗口、图形、音频、网络五个模块,适用于 Windows, Linux, MacOS 等主流 PC 平台,未来还可能扩展到手机平台。SFML 用于快速构建 PC 窗口程序,很适合于开发游戏及多媒体应用,同时它本身也是一个 OpenGL 工具库,可以直接应用于 OpenGL 程序的窗口管理。
简介
SFML 的全称是 Simple and Fast Multimedia Library
,即简单快速的多媒体库,顾名思义就是一个用于开发多媒体应用的库,包含 系统、窗口、图形、音频、网络 五个模块。SFML 为 PC 各个组件提供简单的接口,简化游戏及多媒体应用的开发,目前适用于 PC 的多个平台,包括 Windows,Linux,MacOS,未来可能会拓展到 Android 和 IOS,SFML 是使用 C++ 编写的库,同时官方也绑定了 C,Java,Pyhton,.Net,Go 等多种语言。另外,SFML 内嵌了 OpenGL 模块,很容易用来开发 OpenGL 程序,OpenGL 只是一个底层的图形库,本身没有窗口管理模块,所以需要借助第三方库,常用的库有 glut(freeglut), glfw, qt, sfml
,而 SFML 除了窗口管理,还有系统图形、音频、网络模块,具备了一个完整多媒体应用所需的各个模块,无疑是一个比较好的选择,当然 QT 也是。
SFML 官网地址:https://www.sfml-dev.org, 官方有各个操作系统和各个版本的下载地址,以及 github 源码,以及清晰明了的 API 文档和教程。
环境配置
本文基于 Windows 系统,C++ 语言,Visual Studio 2019 IDE。
SFML 包括 system, window, graphics, audio, network
5 个模块,对应的 Windows 编译库就有下面 5 个库:
sfml-system.lib
:系统库,最基础的库,是其它库的基础依赖;sfml-window.lib
:窗口管理库,依赖于sfml-system.lib
;sfml-graphics.lib
:图形库,依赖于sfml-system.lib
和sfml-window.lib
;sfml-audio.lib
:音频库,依赖于sfml-system.lib
;sfml-network.lib
:网络库,依赖于sfml-system.lib
。
和 Windows 上的其它库一样,这五个库都区分 32 位和 64 位,debug 版本和 release 版本,动态库和静态库,以系统库为例,32 位版本和 64 位版本各有 sfml-system.lib, sfml-system-d.lib, sfml-system-s.lib, sfml-system-s-d.lib
这 4 个 lib 文件,另外还有 sfml-system.dll, sfml-system-d.dll
这 2 个 dll 文件,所以一个库就有 (4 + 2) * 2 = 12 个文件,5 个库就是 60 个文件,emm,是有点多。
可以看到所有库都依赖于系统库,图形库额外还要依赖于窗口库;如果只是想利用 SFML 来创建和管理窗口,则引用 sfml-system, sfml-window
这两个库,如果要使用其内置接口来绘制图形,则再加个 sfml-graphics
库;如果只是单纯的播放音频,则引入 sfml-system, sfml-audio
这两个库,只是单纯是使用网络功能,则引入 sfml-system, sfml-network
这两个库,当然如果使用音频或网络的时候是个 GUI 应用(非控制台应用),那 sfml-window
也是必不可少的。当然除了这五个库,还有其它基础库需要引入,gdi32.lib, opengl32.lib, ogg.lib
,不过这些库 Visual Studio 都会自动帮我们引入,所以不用管,详细的可看官方文档 SFML and Visual Studio,关于 Visual Studio 配置的详细内容,也可看我这篇文章。
创建窗口
1 |
|
首先包含两个库 sfml-system.lib
和 sfml-window.lib
,引入头文件 <sfml/window.hpp>
。使用 sf::Window
类来创建一个窗口,有两种方式,一是在构造函数中直接传入参数,二是先创建一个无数据的对象,再调用 create
静态方法来初始化参数,二者的效果是一样的。窗口接收 4 个参数,
- arg1 的类型为 sf::VideoMode,表示窗口的显示模式;
- arg2 为窗口的标题;
- arg3 为窗口样式,不传则为默认值
sf::Style::Default
; - arg4 为 OpenGL 的具体选项,不传则为空。
VideoMode
VideoMode
包括三个属性 width, height, bitsPerPixel
,分别表示窗口的宽度、高度和深度(每个像素多少 bit),深度不传的话则默认为 32,宽高则必须传。
VideoMode
主要用途是“全屏模式”,它提供一个非常实用的静态方法 getFullscreenModes
,用于获取当前操作系统下可用的全屏显示模式列表(显卡和显示器是否支持),只有使用这个列表里的显示模式,才能正常显示一个全屏窗口。我们玩主机游戏的时候会有一个选择分辨率的窗口,上面列出的分辨率不是随便列的,而是当前系统支持的显示模式。
1 | int main() |
上面例子打印出所有可用的显示模式,并使用其中一个显示模式创建一个全屏窗口。
VideoMode
还提供另一个静态方法 getDesktopMode
,用于获取当前桌面使用的显示模式,这样就能拿到桌面的分辨率和显示深度,从而创建一个和桌面一样分辨率和显示深度的窗口或其它操作。
窗口样式
SFML 提供五种窗口样式,有些样式可以组合,有些则与其它样式互斥。
sfml::Style::None
:无任何装饰,不能与其它样式组合,在一些特殊场合很有用,比如启动界面;sfml::Style:Fullscreen
:全屏样式,全屏显示且无任何装饰,不能与其它样式组合,必须使用系统支持的显示模式;sfml::Style::Titlebar
:带一个标题栏,可与其它样式组合使用;sfml::Style:Resize
:可缩放且提供最大化按钮,可与其它样式组合使用;sfml:Style:Close
:可关闭,提供关闭按钮,可与其它样式组合使用。
默认为第六种样式 sfml:Style:Default
,它是后面三种样式的组合,即 sfml::Style::Titlebar | sfml::Style::Resize | sfml::Style::Close
,有标题栏、最大化按钮和关闭按钮,可调整大小和关闭,统称为窗口样式;因此,SFML 的窗口样式可大致分为三种,无样式、全屏样式和窗口样式。
OpenGL 上下文
创建一个窗口完整的代码如下,
1 | sf::ContextSettings settings; |
VideoMode
和 Style
分别表示显示模式和窗口样式,而最后一个参数 sf::ContextSettings
则是专门为 OpenGL 定制的,用于定义 OpenGL context 的配置,比如 OpenGL 主版本、次版本、深度缓冲区的位数、模块缓冲区的位数、抗锯齿等级,具体的这里先不说,后面讲到 OpenGL 的时候再详细讲,现在只需要知道这个参数可省略就行。
主循环
上面的代码只创建一个“死窗口”,首先在 main
函数创建完窗口之后马上就结束了,所以你会看到一个窗口闪一下就没了,然后程序就退出了,所以需要在创建窗口之后让程序停在某个地方而不是直接退出,即定义一个程序主循环。光让程序停住还不行,这时窗口虽然看到了,但还是个“死窗口”,无法移动、关闭、缩放,因为没有把窗口加入到程序主循环中去。把窗口加入到主循环,其实就是实时监听并处理窗口事件,对窗口的所有操作都是基于事件。
程序主循环最容易想到的就是在 main
方法中加入一个死循环 while(true){ ... }
,唯一要处理的就是什么时候跳出循环,这里我们关闭窗口的时候退出程序,自然是窗口关闭的时候退出主循环,所以主循环的写法为 while(window.isOpen()){ ... }
,而循环内则处理窗口事件。创建一个“活窗口”的完整代码如下:
1 |
|
窗口操作
SFML 的窗口只是提供一个 OpenGL 上下文环境或者 SFML 内部绘制的环境,不具体其它专用的 GUI 库提供的高级功能,但也能做一些基础的操作,比如设置窗口位置、大小、标题、图标等。
1 |
|
设置窗口图标需要使用 Image
对象来加载图片,所以需要引用图形库,即需要 sfml-system.lib, sfml-window.lib, sfml-graphics.lib
三个库,头文件则导入 SFML/Graphics.hpp>
即可。
如果要对窗口做更高级的操作,可以使用其它 GUI 库来创建窗口,然后将 SFML 的绘制环境嵌入进去,这种方式需要第三方创建的窗口返回一个特定操作系统的句柄,然后以这外句柄为参数来创建 SFML 窗口,SFML 窗口可以捕捉到第三方库创建的父窗口的事件,而且不影响父窗口的管理。
1 | sf::WindowHandle handle = /* specific to what you're doing and the library you're using */; |
反过来,也可以使用 SFML 来创建窗口,然后返回一个特定操作系统的句柄,再由其它库操作这个句柄从而实现更高级的窗口操作。
1 | sf::Window window(sf::VideoMode(800, 600), "SFML window"); |
帧率
SFML 有两种设置帧率的方法,第一种是开启显示器垂直同步,第二种是手动设置最大帧率,两种方式选其一,不能同时使用,即不能开启显示器垂直同步的同时又手动设置最大帧率。
通过接口 setVerticalSyncEnabled
开启显示器垂直同步,可解决一些视觉问题,比如撕裂,当应用的刷新频率与显示器不同步时,显示器的上方可能会显示上一帧的内容,而下方显示下一帧的内容,这就出现的画面撕裂的情况;要注意的是这个可能会无效,原因是显示器把垂直同步关了,把显示器设置中的 vertical synchronization
从 off
改为 controlled by application
即可。
1 | window.setVerticalSyncEnabled(true); // call it once, after creating the window |
通过接口 setFramerateLimit
手动设置最大帧率,这是 SFML 内部使用 sf::Clock
和 sf:sleep
实现的,所以不是完全可靠的,特别是高帧率,其取决于特定操作系统和底层硬件,不要用此来实现精准计时。
1 | window.setFramerateLimit(60); // call it once, after creating the window |
注意事项
- SFML 支持一个程序创建多个窗口,每个窗口可以运行在单独的线程上,也可以都运行在主线程上(在 MacOS 上,所以窗口必须运行在主线程上);
- SFML 暂不支持多显示器管理,即多个显示器的时候,你无法决定窗口默认显示在哪个显示器上;
- 窗口比桌面大可能会显示不正常,当然一般也不会创建比桌面还大的窗口,但要注意的是使用
VideoMode:getDesktopMode()
获取的显示模式,再加上边界和标题格栏等装饰物,创建出来的窗口是比桌面大的; - 窗口的事件处理必须和窗口在同一线程,如果实在想分离一些东西出来,可以把渲染、物理、逻辑等分离到其它线程,但窗口管理和该窗口的事件处理必须在同一线程。