小游戏和GUI编程(1) | 基于 SFML 的自由落体小球
文章目录
1. 目的
通过一些简单的例子(2D小游戏的基础代码片段), 来学习 SFML 的使用。
2. SFML 适合做图形显示的理由
使用 SFML 做图形显示的库。 相比于其他用的过库:
- EasyX: 不开源, 不能跨平台使用, API 风格陈旧, 不是 C++ API
- OpenCV 的 highgui 模块: highgui 不是 OpenCV 的最强项, 功能有限
- SDL2: 完全用 C 写的, 不利于让我保持 C++ 语法的熟悉度
- Qt: 有 GPL License 导致的潜在法律问题, 弃用
- Dear imgui: 比较 geek, 默认的字体风格我受不了, “代码即文档” 也难度较大
- SFML: 开源, 跨平台, 现代的 C++, License 友好, 文档直白, 功能齐全
3. 使用 SFML - 构建阶段
目前是 2024 年 2 月 9 日, 使用最新版 SFML 2.6.1。 我是 mac-mini 环境, 安装了 CMake 3.28, C++编译器是苹果自带的 AppleClang 15.0.0。
首先安装 SFML:
brew intall sfml
然后在 CMakeLists.txt 里, 为可执行程序链接 sfml 的库。 SFML 的 cmake 里,要求指明每一个 component:
cmake_minimum_required(VERSION 3.20)
project(free-falling-ball)
set(CMAKE_CXX_STANDARD 11)
add_executable(free-falling-ball
free-falling-ball.cpp
)
find_package(SFML COMPONENTS graphics window system)
target_link_libraries(free-falling-ball PRIVATE sfml-graphics sfml-window sfml-system)
简单起见, free-falling-ball.cpp
里先写一个 hello world, 用于完成构建:
#include <stdio.h>
int main()
{
printf("hello SFML\n");
return 0;
}
执行编译和运行:
cmake -S . -B build
cmake --build build
4. 使用 SFML - C++ 代码
官方给出了创建和管理窗口的文档: Opening and managing a SFML window
4.0 代码布局
在达到最终效果前, 每一步实现一个基础功能, 放在一个 demox_xxx() 的函数中, 在 main 函数里调用它, 后续的每一小节就不列出 main() 了:
int main()
{
demo1_show_window();
return 0;
}
4.1 创建窗口
创建窗口的最简代码如下, 运行的话是一闪而过,但确实是创建了窗口的:
#include <SFML/Window.hpp>
int demo1_show_window()
{
sf::Window window(sf::VideoMode(800, 600), "My Window");
return 0;
}
sf::Window 类
SFML 库中的窗口, 是定义在 sf::Window
类中, 通过包含 SFML/Window.hpp
来引入。 实际上是在 SFML/Window/Window.hpp
中给出类的声明:
class SFML_WINDOW_API Window : public WindowBase, GlResource
{
public:
...
}
而 SFML/Window.hpp
里则是类似于 OpenCV 的 opencv2/opencv.hpp
, 只包含了各个模块的头文件:
#include <SFML/System.hpp>
#include <SFML/Window/Clipboard.hpp>
#include <SFML/Window/Context.hpp>
#include <SFML/Window/ContextSettings.hpp>
#include <SFML/Window/Cursor.hpp>
#include <SFML/Window/Event.hpp>
#include <SFML/Window/Joystick.hpp>
#include <SFML/Window/Keyboard.hpp>
#include <SFML/Window/Mouse.hpp>
#include <SFML/Window/Sensor.hpp>
#include <SFML/Window/Touch.hpp>
#include <SFML/Window/VideoMode.hpp>
#include <SFML/Window/Window.hpp>
#include <SFML/Window/WindowHandle.hpp>
#include <SFML/Window/WindowStyle.hpp>
sf::VideoMode类
sf::VideoMode 类定义在 SFML/Window/VideoMode.hpp
文件中, 构造函数如下, bpp参数默认值是 32, 前两个参数指定了窗口的宽度和高度:
namespace sf
{
class SFML_WINDOW_API VideoMode
{
public:
VideoMode(unsigned int modeWidth, unsigned int modeHeight, unsigned int modeBitsPerPixel = 32);
...
}
重构后的代码
让变量尽可能有意义, 避免硬编码:
int demo1_show_window_refactored()
{
constexpr int win_width = 600;
constexpr int win_height = 600;
const std::string title = "Free falling ball";
sf::Window window(sf::VideoMode(win_width, win_height), title);
return 0;
}
4.2 循环显示窗口, 并处理关闭事件
如下代码创建的窗口, 能够持续显示, 并且带有最小化、最大化、关闭按钮, 能用鼠标点击关闭按钮后关闭窗口:
int demo2_show_window_with_loop()
{
constexpr int win_width = 600;
constexpr int win_height = 600;
const std::string title = "Free falling ball";
sf::Window window(sf::VideoMode(win_width, win_height), title);
// run the program as long as the window is open
while (window.isOpen())
{
// check all the window's evetnts that were triggered since the last iteration of the loop
sf::Event event;
while (window.pollEvent(event))
{
if (event.type == sf::Event::Closed)
{
window.close();
}
}
}
return 0;
}
首先, 增加了一个外层循环 while(window.isOpen())
, 用来确保只要窗口没有被关闭, 就持续的刷新显示。 绝大多数的 SFML 窗口程序都有这个循环, 也叫做 “main loop” 或 “game loop”.
内存循环, 是处理所有的窗口事件, 意思是说如果有多个事件, 比如同时做了鼠标和键盘的操作, 都会被处理。
window.pollEvent()
函数返回 bool 类型, 如果现在还有没被处理过的事件, 它返回 true, 如果所有事件都处理完了, 它返回 false。
这里我们只处理了 sf::Event::Closed
事件, SFML/Window/Event.hpp
里定义了 EventType
枚举类型: Closed
的注释写的很清晰, 是窗口关闭的请求。
/// \brief Enumeration of the different types of events
///
enum EventType
{
Closed, //!< The window requested to be closed (no data)
Resized, //!< The window was resized (data in event.size)
LostFocus, //!< The window lost the focus (no data)
GainedFocus, //!< The window gained the focus (no data)
TextEntered, //!< A character was entered (data in event.text)
KeyPressed